mirror of
https://github.com/dessant/buster.git
synced 2024-11-13 19:10:34 +00:00
feat: simulate user input
This commit is contained in:
parent
3398166e83
commit
779f466a50
2
.babelrc
2
.babelrc
@ -1,5 +1,5 @@
|
||||
{
|
||||
"presets": ["@babel/env"],
|
||||
"presets": [["@babel/env", {"useBuiltIns": "usage"}]],
|
||||
"env": {
|
||||
"production": {
|
||||
"plugins": ["lodash"]
|
||||
|
@ -14,7 +14,7 @@
|
||||
"build:prod:chrome": "cross-env TARGET_ENV=chrome yarn _build:prod",
|
||||
"build:prod:firefox": "cross-env TARGET_ENV=firefox yarn _build:prod",
|
||||
"build:prod:opera": "cross-env TARGET_ENV=opera yarn _build:prod",
|
||||
"build:prod:all": "run-s 'build:@(chrome|firefox|opera)'",
|
||||
"build:prod:all": "run-s 'build:prod:@(chrome|firefox|opera)'",
|
||||
"_build:prod:zip": "yarn _build:prod && gulp zip",
|
||||
"build:prod:zip:chrome": "cross-env TARGET_ENV=chrome yarn _build:prod:zip",
|
||||
"build:prod:zip:firefox": "cross-env TARGET_ENV=firefox yarn _build:prod:zip",
|
||||
@ -39,10 +39,12 @@
|
||||
"@material/theme": "^0.30.0",
|
||||
"@material/typography": "^0.28.0",
|
||||
"audiobuffer-to-wav": "^1.0.0",
|
||||
"bowser": "^2.1.0",
|
||||
"ext-components": "dessant/ext-components#^0.1.6",
|
||||
"ext-contribute": "dessant/ext-contribute#^0.1.6",
|
||||
"storage-versions": "dessant/storage-versions#^0.2.4",
|
||||
"typeface-roboto": "^0.0.54",
|
||||
"uuid": "^3.3.2",
|
||||
"vue": "^2.5.17",
|
||||
"webextension-polyfill": "^0.3.1"
|
||||
},
|
||||
|
@ -139,6 +139,11 @@
|
||||
"description": "Title of the option."
|
||||
},
|
||||
|
||||
"optionTitle_simulateUserInput": {
|
||||
"message": "Simulate user input",
|
||||
"description": "Title of the option."
|
||||
},
|
||||
|
||||
"optionTitle_witSpeechApiLang": {
|
||||
"message": "API language",
|
||||
"description": "Title of the option."
|
||||
@ -420,6 +425,16 @@
|
||||
}
|
||||
},
|
||||
|
||||
"inputLabel_appLocation": {
|
||||
"message": "App location",
|
||||
"description": "Label of the input."
|
||||
},
|
||||
|
||||
"inputLabel_manifestLocation": {
|
||||
"message": "Manifest location",
|
||||
"description": "Label of the input."
|
||||
},
|
||||
|
||||
"buttonText_addApi": {
|
||||
"message": "Add API",
|
||||
"description": "Text of the button."
|
||||
@ -435,6 +450,61 @@
|
||||
"description": "Text of the button."
|
||||
},
|
||||
|
||||
"buttonText_downloadApp": {
|
||||
"message": "Download app",
|
||||
"description": "Text of the button."
|
||||
},
|
||||
|
||||
"buttonText_installApp": {
|
||||
"message": "Install app",
|
||||
"description": "Text of the button."
|
||||
},
|
||||
|
||||
"buttonText_goBack": {
|
||||
"message": "Go back",
|
||||
"description": "Text of the button."
|
||||
},
|
||||
|
||||
"pageContent_optionClientAppDownloadDesc": {
|
||||
"message": "Download and install the client app for user input simulation.",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageContent_optionClientAppOSError": {
|
||||
"message": "Your operating system is not supported.",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageContent_installTitle": {
|
||||
"message": "Install Buster Client",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageContent_installDesc": {
|
||||
"message": "The client app enables Buster to simulate user input and helps lower the occurrence of difficult challenges and temporary blocks.",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageContent_installSuccessTitle": {
|
||||
"message": "Installation finished",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageContent_installSuccessDesc": {
|
||||
"message": "The client app has been installed for the current browser.",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageContent_installErrorTitle": {
|
||||
"message": "Something went wrong",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageContent_installErrorDesc": {
|
||||
"message": "The installation has failed. Check the browser console for more details, and open an issue on GitHub if this error persists.",
|
||||
"description": "Page content."
|
||||
},
|
||||
|
||||
"pageTitle": {
|
||||
"message": "$PAGETITLE$ - $EXTENSIONNAME$",
|
||||
"description": "Title of the page.",
|
||||
@ -475,6 +545,11 @@
|
||||
"description": "Error message."
|
||||
},
|
||||
|
||||
"error_missingNativeApp": {
|
||||
"message": "Cannot connect to native app. Finish setting up the application or turn off user input simulation from the options page.",
|
||||
"description": "Error message."
|
||||
},
|
||||
|
||||
"error_internalError": {
|
||||
"message": "Something went wrong. Open the browser console for more details.",
|
||||
"description": "Error message."
|
||||
|
@ -2,13 +2,96 @@ import browser from 'webextension-polyfill';
|
||||
|
||||
import {initStorage} from 'storage/init';
|
||||
import storage from 'storage/storage';
|
||||
import {showNotification, showContributePage} from 'utils/app';
|
||||
import {
|
||||
showNotification,
|
||||
showContributePage,
|
||||
sendNativeMessage
|
||||
} from 'utils/app';
|
||||
import {
|
||||
executeCode,
|
||||
executeFile,
|
||||
scriptsAllowed,
|
||||
functionInContext
|
||||
} from 'utils/common';
|
||||
import {clientAppApiVersion} from 'utils/config';
|
||||
|
||||
let nativePort;
|
||||
|
||||
function getFrameClientPos(index) {
|
||||
let currentIndex = -1;
|
||||
if (window !== window.top) {
|
||||
const siblingWindows = window.parent.frames;
|
||||
for (let i = 0; i < siblingWindows.length; i++) {
|
||||
if (siblingWindows[i] === window) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const targetWindow = window.frames[index];
|
||||
for (const frame of document.querySelectorAll('iframe')) {
|
||||
if (frame.contentWindow === targetWindow) {
|
||||
let {left: x, top: y} = frame.getBoundingClientRect();
|
||||
const scale = window.devicePixelRatio;
|
||||
|
||||
return {x: x * scale, y: y * scale, currentIndex};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getFramePos(tabId, frameId, frameIndex) {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
while (true) {
|
||||
frameId = (await browser.webNavigation.getFrame({
|
||||
tabId,
|
||||
frameId
|
||||
})).parentFrameId;
|
||||
if (frameId === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
const [data] = await executeCode(
|
||||
`(${getFrameClientPos.toString()})(${frameIndex})`,
|
||||
tabId,
|
||||
frameId
|
||||
);
|
||||
|
||||
frameIndex = data.currentIndex;
|
||||
x += data.x;
|
||||
y += data.y;
|
||||
}
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
async function resetCaptcha(tabId, frameId, challengeUrl) {
|
||||
frameId = (await browser.webNavigation.getFrame({
|
||||
tabId,
|
||||
frameId: frameId
|
||||
})).parentFrameId;
|
||||
|
||||
if (!(await scriptsAllowed(tabId, frameId))) {
|
||||
await showNotification({messageId: 'error_scriptsNotAllowed'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await functionInContext('addListener', tabId, frameId))) {
|
||||
await executeFile('/src/content/initReset.js', tabId, frameId);
|
||||
}
|
||||
await executeCode('addListener()', tabId, frameId);
|
||||
|
||||
await browser.tabs.sendMessage(
|
||||
tabId,
|
||||
{
|
||||
id: 'resetCaptcha',
|
||||
challengeUrl
|
||||
},
|
||||
{frameId}
|
||||
);
|
||||
}
|
||||
|
||||
function challengeRequestCallback(details) {
|
||||
const url = new URL(details.url);
|
||||
@ -19,12 +102,12 @@ function challengeRequestCallback(details) {
|
||||
}
|
||||
|
||||
async function setChallengeLocale() {
|
||||
const {loadEnglishChallenge} = await storage.get(
|
||||
'loadEnglishChallenge',
|
||||
const {loadEnglishChallenge, simulateUserInput} = await storage.get(
|
||||
['loadEnglishChallenge', 'simulateUserInput'],
|
||||
'sync'
|
||||
);
|
||||
|
||||
if (loadEnglishChallenge) {
|
||||
if (loadEnglishChallenge || simulateUserInput) {
|
||||
if (
|
||||
!browser.webRequest.onBeforeRequest.hasListener(challengeRequestCallback)
|
||||
) {
|
||||
@ -63,30 +146,21 @@ async function onMessage(request, sender) {
|
||||
await showContributePage('use');
|
||||
}
|
||||
} else if (request.id === 'resetCaptcha') {
|
||||
const tabId = sender.tab.id;
|
||||
const frameId = (await browser.webNavigation.getFrame({
|
||||
tabId,
|
||||
frameId: sender.frameId
|
||||
})).parentFrameId;
|
||||
|
||||
if (!(await scriptsAllowed(tabId, frameId))) {
|
||||
await showNotification({messageId: 'error_scriptsNotAllowed'});
|
||||
return;
|
||||
await resetCaptcha(sender.tab.id, sender.frameId, request.challengeUrl);
|
||||
} else if (request.id === 'getFramePos') {
|
||||
return getFramePos(sender.tab.id, sender.frameId, request.index);
|
||||
} else if (request.id === 'startNativeApp') {
|
||||
nativePort = browser.runtime.connectNative('org.buster.client');
|
||||
} else if (request.id === 'stopNativeApp') {
|
||||
if (nativePort) {
|
||||
nativePort.disconnect();
|
||||
}
|
||||
|
||||
if (!(await functionInContext('addListener', tabId, frameId))) {
|
||||
await executeFile('/src/content/initReset.js', tabId, frameId);
|
||||
}
|
||||
await executeCode('addListener()', tabId, frameId);
|
||||
|
||||
await browser.tabs.sendMessage(
|
||||
tabId,
|
||||
{
|
||||
id: 'resetCaptcha',
|
||||
challengeUrl: request.challengeUrl
|
||||
},
|
||||
{frameId}
|
||||
);
|
||||
} else if (request.id === 'sendNativeMessage') {
|
||||
const message = {
|
||||
apiVersion: clientAppApiVersion,
|
||||
...request.message
|
||||
};
|
||||
return await sendNativeMessage(nativePort, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
14
src/content/install.js
Normal file
14
src/content/install.js
Normal file
@ -0,0 +1,14 @@
|
||||
function install() {
|
||||
const url = new URL(chrome.extension.getURL('/src/install/index.html'));
|
||||
url.searchParams.set(
|
||||
'session',
|
||||
new URL(window.location.href).searchParams.get('session')
|
||||
);
|
||||
url.searchParams.set('port', window.location.port);
|
||||
|
||||
const frame = document.createElement('iframe');
|
||||
frame.src = url.href;
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
|
||||
install();
|
@ -1,11 +1,17 @@
|
||||
(function() {
|
||||
const onMessage = function(e) {
|
||||
e.stopImmediatePropagation();
|
||||
window.clearTimeout(timeoutId);
|
||||
|
||||
const challengeUrl = e.detail;
|
||||
for (const [k, v] of Object.entries(___grecaptcha_cfg.clients)) {
|
||||
if (v['O'].D.src === challengeUrl) {
|
||||
grecaptcha.reset(k);
|
||||
break;
|
||||
for (const [k, client] of Object.entries(___grecaptcha_cfg.clients)) {
|
||||
for (const [_, items] of Object.entries(client)) {
|
||||
for (const [_, v] of Object.entries(items)) {
|
||||
if (v instanceof Element && v.src === challengeUrl) {
|
||||
grecaptcha.reset(k);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
239
src/install/App.vue
Normal file
239
src/install/App.vue
Normal file
@ -0,0 +1,239 @@
|
||||
<!-- prettier-ignore -->
|
||||
<template>
|
||||
<div id="app" v-if="dataLoaded">
|
||||
<div class="wrap" v-if="!isInstallSuccess && !isInstallError">
|
||||
<div class="title">
|
||||
{{ getText('buttonText_installApp') }}
|
||||
</div>
|
||||
<div class="desc">
|
||||
{{ getText('pageContent_installDesc') }}
|
||||
</div>
|
||||
|
||||
<v-textfield
|
||||
v-model.trim="appDir"
|
||||
:label="getText('inputLabel_appLocation')">
|
||||
</v-textfield>
|
||||
<v-textfield
|
||||
v-model.trim="manifestDir"
|
||||
:label="getText('inputLabel_manifestLocation')">
|
||||
</v-textfield>
|
||||
|
||||
<v-button class="button install-button"
|
||||
:unelevated="true"
|
||||
:disabled="isInstalling || !appDir || !manifestDir"
|
||||
@click="runInstall">
|
||||
{{ getText('buttonText_installApp') }}
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<div class="wrap" v-if="isInstallSuccess">
|
||||
<div class="title">{{ getText('pageContent_installSuccessTitle') }}</div>
|
||||
<div class="desc">{{ getText('pageContent_installSuccessDesc') }}</div>
|
||||
|
||||
<div class="success-icon">🎉</div>
|
||||
</div>
|
||||
|
||||
<div class="wrap" v-if="isInstallError">
|
||||
<div class="title error-title">{{ getText('pageContent_installErrorTitle') }}</div>
|
||||
<div class="desc">{{ getText('pageContent_installErrorDesc') }}</div>
|
||||
|
||||
<v-button class="button error-button"
|
||||
:unelevated="true"
|
||||
@click="isInstallError = false">
|
||||
{{ getText('buttonText_goBack') }}
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import browser from 'webextension-polyfill';
|
||||
import {Button, TextField} from 'ext-components';
|
||||
|
||||
import storage from 'storage/storage';
|
||||
import {pingClientApp} from 'utils/app';
|
||||
import {getText, getBrowser} from 'utils/common';
|
||||
import {targetEnv} from 'utils/config';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
[Button.name]: Button,
|
||||
[TextField.name]: TextField
|
||||
},
|
||||
|
||||
data: function() {
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
const apiURL = new URL('http://127.0.0.1/api/v1');
|
||||
apiURL.port = urlParams.get('port');
|
||||
|
||||
return {
|
||||
dataLoaded: false,
|
||||
|
||||
apiUrl: apiURL.href,
|
||||
session: urlParams.get('session'),
|
||||
appDir: '',
|
||||
manifestDir: '',
|
||||
|
||||
isInstalling: false,
|
||||
isInstallSuccess: false,
|
||||
isInstallError: false
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
getText,
|
||||
|
||||
getExtensionId: function() {
|
||||
let id = browser.runtime.id;
|
||||
if (targetEnv !== 'firefox') {
|
||||
const scheme = window.location.protocol;
|
||||
id = `${scheme}//${id}/`;
|
||||
}
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
setLocation: async function() {
|
||||
try {
|
||||
await this.location();
|
||||
} catch (err) {
|
||||
this.isInstallError = true;
|
||||
console.log(err.toString());
|
||||
}
|
||||
},
|
||||
|
||||
runInstall: async function() {
|
||||
this.isInstalling = true;
|
||||
|
||||
try {
|
||||
await this.install();
|
||||
} catch (err) {
|
||||
this.isInstallError = true;
|
||||
console.log(err.toString());
|
||||
} finally {
|
||||
this.isInstalling = false;
|
||||
}
|
||||
},
|
||||
|
||||
location: async function() {
|
||||
const data = new FormData();
|
||||
data.append('session', this.session);
|
||||
data.append('browser', (await getBrowser()).name);
|
||||
data.append('targetEnv', targetEnv);
|
||||
|
||||
const rsp = await fetch(`${this.apiUrl}/install/location`, {
|
||||
referrer: '',
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
|
||||
const results = await rsp.json();
|
||||
|
||||
if (rsp.status === 200) {
|
||||
this.appDir = results.appDir;
|
||||
this.manifestDir = results.manifestDir;
|
||||
} else {
|
||||
throw new Error(results.error);
|
||||
}
|
||||
},
|
||||
|
||||
install: async function() {
|
||||
const data = new FormData();
|
||||
data.append('session', this.session);
|
||||
data.append('appDir', this.appDir);
|
||||
data.append('manifestDir', this.manifestDir);
|
||||
data.append('targetEnv', targetEnv);
|
||||
data.append('extension', this.getExtensionId());
|
||||
|
||||
const rsp = await fetch(`${this.apiUrl}/install/run`, {
|
||||
referrer: '',
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
|
||||
if (rsp.status === 200) {
|
||||
await pingClientApp();
|
||||
await storage.set({simulateUserInput: true}, 'sync');
|
||||
|
||||
this.isInstallSuccess = true;
|
||||
} else {
|
||||
throw new Error((await rsp.json()).error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created: async function() {
|
||||
await this.setLocation();
|
||||
|
||||
this.dataLoaded = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$mdc-theme-primary: #1abc9c;
|
||||
|
||||
@import '@material/theme/mixins';
|
||||
@import '@material/typography/mixins';
|
||||
@import '@material/button/mixins';
|
||||
|
||||
body {
|
||||
@include mdc-typography-base;
|
||||
font-size: 100%;
|
||||
background-color: #ecf0f1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.title,
|
||||
.desc {
|
||||
@include mdc-theme-prop('color', 'text-primary-on-light');
|
||||
}
|
||||
|
||||
.title {
|
||||
@include mdc-typography('title');
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.desc {
|
||||
@include mdc-typography('body1');
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.button {
|
||||
@include mdc-button-ink-color(#fff);
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.install-button {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.error-button {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 72px;
|
||||
margin-top: 36px;
|
||||
}
|
||||
</style>
|
18
src/install/index.html
Normal file
18
src/install/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="/src/icons/app/icon-16.png" type="image/png">
|
||||
<link href="/src/fonts/roboto.css" rel="stylesheet">
|
||||
<link href="/src/commons-ui/style.css" rel="stylesheet">
|
||||
<link href="style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="/src/manifest.js"></script>
|
||||
<script src="/src/commons-ui/script.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
20
src/install/main.js
Normal file
20
src/install/main.js
Normal file
@ -0,0 +1,20 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
import App from './App';
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
await document.fonts.load('400 14px Roboto');
|
||||
await document.fonts.load('500 14px Roboto');
|
||||
} catch (e) {}
|
||||
|
||||
const vm = new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
});
|
||||
}
|
||||
|
||||
// only run in a frame
|
||||
if (window.top !== window) {
|
||||
init();
|
||||
}
|
@ -20,14 +20,16 @@
|
||||
"storage",
|
||||
"tabs",
|
||||
"activeTab",
|
||||
"contextMenus",
|
||||
"notifications",
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"webNavigation",
|
||||
"nativeMessaging",
|
||||
"<all_urls>"
|
||||
],
|
||||
|
||||
"content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; object-src 'none';",
|
||||
"content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; object-src 'none'; frame-ancestors http://127.0.0.1:*;",
|
||||
|
||||
"icons": {
|
||||
"16": "src/icons/app/icon-16.png",
|
||||
@ -48,6 +50,11 @@
|
||||
"run_at": "document_idle",
|
||||
"css": ["src/solve/style.css"],
|
||||
"js": ["src/manifest.js", "src/solve/script.js"]
|
||||
},
|
||||
{
|
||||
"matches": ["http://127.0.0.1/buster/install?session=*"],
|
||||
"run_at": "document_idle",
|
||||
"js": ["src/content/install.js"]
|
||||
}
|
||||
],
|
||||
|
||||
@ -62,5 +69,5 @@
|
||||
"page": "src/background/index.html"
|
||||
},
|
||||
|
||||
"web_accessible_resources": ["src/content/reset.js"]
|
||||
"web_accessible_resources": ["src/install/index.html", "src/content/reset.js"]
|
||||
}
|
||||
|
@ -85,12 +85,40 @@
|
||||
<v-switch id="lec" v-model="options.loadEnglishChallenge"></v-switch>
|
||||
</v-form-field>
|
||||
</div>
|
||||
<div class="option">
|
||||
|
||||
<div class="option"
|
||||
v-if="!options.loadEnglishChallenge">
|
||||
<v-form-field input-id="esm"
|
||||
:label="getText('optionTitle_tryEnglishSpeechModel')">
|
||||
<v-switch id="esm" v-model="options.tryEnglishSpeechModel"></v-switch>
|
||||
</v-form-field>
|
||||
</div>
|
||||
|
||||
<div class="option">
|
||||
<v-form-field input-id="si"
|
||||
:label="getText('optionTitle_simulateUserInput')">
|
||||
<v-switch id="si" v-model="options.simulateUserInput"></v-switch>
|
||||
</v-form-field>
|
||||
</div>
|
||||
<div class="client-bownload" v-if="showClientAppNotice">
|
||||
<div class="download-desc">
|
||||
{{ getText('pageContent_optionClientAppDownloadDesc') }}
|
||||
</div>
|
||||
<div class="download-error" v-if="!clientAppDownloadUrl">
|
||||
{{ getText('pageContent_optionClientAppOSError') }}
|
||||
</div>
|
||||
|
||||
<v-button class="download-button"
|
||||
:unelevated="true"
|
||||
:disabled="!clientAppDownloadUrl"
|
||||
@click="$refs.dlLink.click()">
|
||||
{{ getText('buttonText_downloadApp') }}
|
||||
</v-button>
|
||||
<a ref="dlLink" class="download-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
:href="clientAppDownloadUrl"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -101,9 +129,13 @@ import browser from 'webextension-polyfill';
|
||||
import {Button, Select, Switch, FormField, TextField} from 'ext-components';
|
||||
|
||||
import storage from 'storage/storage';
|
||||
import {getOptionLabels} from 'utils/app';
|
||||
import {getText} from 'utils/common';
|
||||
import {optionKeys, captchaWitSpeechApiLangCodes} from 'utils/data';
|
||||
import {getOptionLabels, pingClientApp} from 'utils/app';
|
||||
import {getText, getPlatform} from 'utils/common';
|
||||
import {
|
||||
optionKeys,
|
||||
clientAppPlatforms,
|
||||
captchaWitSpeechApiLangCodes
|
||||
} from 'utils/data';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -154,6 +186,9 @@ export default {
|
||||
witSpeechApiLang: '',
|
||||
witSpeechApis: [],
|
||||
|
||||
showClientAppNotice: false,
|
||||
clientAppDownloadUrl: '',
|
||||
|
||||
options: {
|
||||
speechService: '',
|
||||
googleSpeechApiKey: '',
|
||||
@ -163,11 +198,37 @@ export default {
|
||||
microsoftSpeechApiKey: '',
|
||||
witSpeechApiKeys: {},
|
||||
loadEnglishChallenge: false,
|
||||
tryEnglishSpeechModel: false
|
||||
tryEnglishSpeechModel: false,
|
||||
simulateUserInput: false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
'options.simulateUserInput': async function(value) {
|
||||
if (value) {
|
||||
try {
|
||||
await pingClientApp();
|
||||
} catch (e) {
|
||||
if (!this.clientAppDownloadUrl) {
|
||||
const {os, arch} = await getPlatform();
|
||||
if (clientAppPlatforms.includes(`${os}/${arch}`)) {
|
||||
this.clientAppDownloadUrl = `https://github.com/dessant/buster-client/releases/download/v0.1.0/buster-client-v0.1.0-${os}-${arch}`;
|
||||
if (os === 'windows') {
|
||||
this.clientAppDownloadUrl += '.exe';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.showClientAppNotice = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.showClientAppNotice = false;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getText,
|
||||
|
||||
@ -224,6 +285,7 @@ $mdc-theme-primary: #1abc9c;
|
||||
|
||||
@import '@material/theme/mixins';
|
||||
@import '@material/typography/mixins';
|
||||
@import '@material/button/mixins';
|
||||
|
||||
body {
|
||||
@include mdc-typography-base;
|
||||
@ -289,4 +351,31 @@ body {
|
||||
align-self: end;
|
||||
margin-left: 36px;
|
||||
}
|
||||
|
||||
.client-bownload {
|
||||
margin-left: 48px;
|
||||
}
|
||||
|
||||
.download-desc,
|
||||
.download-error {
|
||||
@include mdc-theme-prop('color', 'text-primary-on-light');
|
||||
@include mdc-typography('body1');
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.download-error {
|
||||
margin-top: 12px;
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.download-link {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
@include mdc-button-ink-color(#fff);
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
|
@ -2,7 +2,13 @@ import browser from 'webextension-polyfill';
|
||||
import audioBufferToWav from 'audiobuffer-to-wav';
|
||||
|
||||
import storage from 'storage/storage';
|
||||
import {getText, waitForElement, arrayBufferToBase64} from 'utils/common';
|
||||
import {meanSleep} from 'utils/app';
|
||||
import {
|
||||
getText,
|
||||
waitForElement,
|
||||
arrayBufferToBase64,
|
||||
getRandomFloat
|
||||
} from 'utils/common';
|
||||
import {
|
||||
captchaGoogleSpeechApiLangCodes,
|
||||
captchaIbmSpeechApiLangCodes,
|
||||
@ -47,11 +53,6 @@ function syncUI() {
|
||||
button.id = 'reset-button';
|
||||
|
||||
button.addEventListener('click', resetCaptcha);
|
||||
button.addEventListener('keydown', e => {
|
||||
if (['Enter', ' '].includes(e.key)) {
|
||||
resetCaptcha();
|
||||
}
|
||||
});
|
||||
|
||||
div.appendChild(button);
|
||||
document.querySelector('.rc-footer').appendChild(div);
|
||||
@ -75,12 +76,7 @@ function syncUI() {
|
||||
button.classList.add('working');
|
||||
}
|
||||
|
||||
button.addEventListener('click', start);
|
||||
button.addEventListener('keydown', e => {
|
||||
if (['Enter', ' '].includes(e.key)) {
|
||||
start(e);
|
||||
}
|
||||
});
|
||||
button.addEventListener('click', solveChallenge);
|
||||
|
||||
div.appendChild(button);
|
||||
document.querySelector('.rc-buttons').appendChild(div);
|
||||
@ -138,6 +134,112 @@ function dispatchEnter(node) {
|
||||
node.click();
|
||||
}
|
||||
|
||||
async function navigateToElement(node, {forward = true} = {}) {
|
||||
if (document.activeElement === node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!forward) {
|
||||
await sendNativeMessage({command: 'pressKey', data: 'shift'});
|
||||
await meanSleep(300);
|
||||
}
|
||||
|
||||
while (document.activeElement !== node) {
|
||||
await sendNativeMessage({command: 'tapKey', data: 'tab'});
|
||||
await meanSleep(300);
|
||||
}
|
||||
|
||||
if (!forward) {
|
||||
await sendNativeMessage({command: 'releaseKey', data: 'shift'});
|
||||
await meanSleep(300);
|
||||
}
|
||||
}
|
||||
|
||||
async function tapEnter(node, {navigateForward = true} = {}) {
|
||||
await navigateToElement(node, {forward: navigateForward});
|
||||
await meanSleep(200);
|
||||
await sendNativeMessage({command: 'tapKey', data: 'enter'});
|
||||
}
|
||||
|
||||
async function clickElement(node, browserBorder) {
|
||||
const targetPos = await getClickPos(node, browserBorder);
|
||||
await sendNativeMessage({command: 'moveMouse', ...targetPos});
|
||||
await meanSleep(100);
|
||||
await sendNativeMessage({command: 'clickMouse'});
|
||||
}
|
||||
|
||||
async function sendNativeMessage(message) {
|
||||
const rsp = await browser.runtime.sendMessage({
|
||||
id: 'sendNativeMessage',
|
||||
message
|
||||
});
|
||||
|
||||
if (!rsp.success) {
|
||||
throw new Error(`Native response: ${rsp.text}`);
|
||||
}
|
||||
|
||||
return rsp;
|
||||
}
|
||||
|
||||
async function getBrowserBorder(clickEvent) {
|
||||
const framePos = await getFrameClientPos();
|
||||
const scale = window.devicePixelRatio;
|
||||
|
||||
return {
|
||||
left:
|
||||
clickEvent.screenX -
|
||||
clickEvent.clientX * scale -
|
||||
framePos.x -
|
||||
window.screenX * scale,
|
||||
top:
|
||||
clickEvent.screenY -
|
||||
clickEvent.clientY * scale -
|
||||
framePos.y -
|
||||
window.screenY * scale
|
||||
};
|
||||
}
|
||||
|
||||
async function getFrameClientPos() {
|
||||
if (window !== window.top) {
|
||||
let index;
|
||||
const siblingWindows = window.parent.frames;
|
||||
for (let i = 0; i < siblingWindows.length; i++) {
|
||||
if (siblingWindows[i] === window) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return await browser.runtime.sendMessage({id: 'getFramePos', index});
|
||||
}
|
||||
|
||||
return {x: 0, y: 0};
|
||||
}
|
||||
// window.devicePixelRatio
|
||||
async function getElementScreenRect(node, browserBorder) {
|
||||
let {left: x, top: y, width, height} = node.getBoundingClientRect();
|
||||
|
||||
const data = await getFrameClientPos();
|
||||
const scale = window.devicePixelRatio;
|
||||
|
||||
x *= scale;
|
||||
y *= scale;
|
||||
|
||||
x += data.x + browserBorder.left + window.screenX * scale;
|
||||
y += data.y + browserBorder.top + window.screenY * scale;
|
||||
|
||||
return {x, y, width: width * scale, height: height * scale};
|
||||
}
|
||||
|
||||
async function getClickPos(node, browserBorder) {
|
||||
let {x, y, width, height} = await getElementScreenRect(node, browserBorder);
|
||||
|
||||
return {
|
||||
x: Math.round(x + width * getRandomFloat(0.4, 0.6)),
|
||||
y: Math.round(y + height * getRandomFloat(0.4, 0.6))
|
||||
};
|
||||
}
|
||||
|
||||
async function getWitSpeechApiKey(speechService, language) {
|
||||
if (speechService === 'witSpeechApiDemo') {
|
||||
return witApiKeys[language];
|
||||
@ -223,25 +325,42 @@ async function getMicrosoftSpeechApiResult(
|
||||
}
|
||||
}
|
||||
|
||||
async function solve() {
|
||||
let audioUrl;
|
||||
async function solve(simulateUserInput, clickEvent) {
|
||||
let solution;
|
||||
|
||||
if (isBlocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const audioEl = document.querySelector('#audio-source');
|
||||
if (audioEl) {
|
||||
audioUrl = audioEl.src;
|
||||
} else {
|
||||
dispatchEnter(document.querySelector('button#recaptcha-audio-button'));
|
||||
let browserBorder;
|
||||
let useMouse = true;
|
||||
if (simulateUserInput) {
|
||||
if (clickEvent.clientX || clickEvent.clientY) {
|
||||
browserBorder = await getBrowserBorder(clickEvent);
|
||||
} else {
|
||||
useMouse = false;
|
||||
}
|
||||
}
|
||||
|
||||
const audioLinkSelector = 'a.rc-audiochallenge-tdownload-link';
|
||||
let audioEl = document.querySelector(audioLinkSelector);
|
||||
if (!audioEl) {
|
||||
const audioButton = document.querySelector('#recaptcha-audio-button');
|
||||
if (simulateUserInput) {
|
||||
if (useMouse) {
|
||||
await clickElement(audioButton, browserBorder);
|
||||
} else {
|
||||
await tapEnter(audioButton, {navigateForward: false});
|
||||
}
|
||||
} else {
|
||||
dispatchEnter(audioButton);
|
||||
}
|
||||
|
||||
const result = await Promise.race([
|
||||
new Promise(resolve => {
|
||||
waitForElement('#audio-source', {timeout: 10000}).then(el =>
|
||||
resolve({audioUrl: el && el.src})
|
||||
);
|
||||
waitForElement(audioLinkSelector, {timeout: 10000}).then(el => {
|
||||
meanSleep(500).then(() => resolve({audioEl: el}));
|
||||
});
|
||||
}),
|
||||
new Promise(resolve => {
|
||||
isBlocked({timeout: 10000}).then(blocked => resolve({blocked}));
|
||||
@ -252,9 +371,27 @@ async function solve() {
|
||||
return;
|
||||
}
|
||||
|
||||
audioUrl = result.audioUrl;
|
||||
audioEl = result.audioEl;
|
||||
}
|
||||
|
||||
if (simulateUserInput) {
|
||||
if (useMouse) {
|
||||
audioEl.addEventListener('click', e => e.preventDefault(), {
|
||||
capture: true,
|
||||
once: true
|
||||
});
|
||||
await clickElement(audioEl, browserBorder);
|
||||
} else {
|
||||
audioEl.addEventListener('keydown', e => e.preventDefault(), {
|
||||
capture: true,
|
||||
once: true
|
||||
});
|
||||
await tapEnter(audioEl);
|
||||
}
|
||||
}
|
||||
|
||||
const audioUrl = audioEl.href;
|
||||
|
||||
const lang = document.documentElement.lang;
|
||||
const audioRsp = await fetch(audioUrl, {referrer: ''});
|
||||
const audioContent = await prepareAudio(await audioRsp.arrayBuffer());
|
||||
@ -422,35 +559,81 @@ async function solve() {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelector('#audio-response').value = solution;
|
||||
dispatchEnter(document.querySelector('#recaptcha-verify-button'));
|
||||
const input = document.querySelector('#audio-response');
|
||||
if (simulateUserInput) {
|
||||
if (useMouse) {
|
||||
await clickElement(input, browserBorder);
|
||||
} else {
|
||||
await navigateToElement(input, {forward: false});
|
||||
}
|
||||
await meanSleep(200);
|
||||
|
||||
await sendNativeMessage({command: 'typeText', data: solution});
|
||||
} else {
|
||||
input.value = solution;
|
||||
}
|
||||
|
||||
const submitButton = document.querySelector('#recaptcha-verify-button');
|
||||
if (simulateUserInput) {
|
||||
if (useMouse) {
|
||||
await clickElement(submitButton, browserBorder);
|
||||
} else {
|
||||
await tapEnter(submitButton);
|
||||
}
|
||||
} else {
|
||||
dispatchEnter(submitButton);
|
||||
}
|
||||
|
||||
browser.runtime.sendMessage({id: 'captchaSolved'});
|
||||
}
|
||||
|
||||
function start(e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
function solveChallenge(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
|
||||
if (solverWorking) {
|
||||
if (!ev.isTrusted || solverWorking) {
|
||||
return;
|
||||
}
|
||||
setSolverState({working: true});
|
||||
|
||||
solve()
|
||||
.then(() => {
|
||||
setSolverState({working: false});
|
||||
})
|
||||
runSolver(ev)
|
||||
.catch(err => {
|
||||
setSolverState({working: false});
|
||||
console.log(err.toString());
|
||||
browser.runtime.sendMessage({
|
||||
id: 'notification',
|
||||
messageId: 'error_internalError'
|
||||
});
|
||||
console.log(err.toString());
|
||||
throw err;
|
||||
})
|
||||
.finally(() => {
|
||||
setSolverState({working: false});
|
||||
});
|
||||
}
|
||||
|
||||
async function runSolver(ev) {
|
||||
const {simulateUserInput} = await storage.get('simulateUserInput', 'sync');
|
||||
|
||||
if (simulateUserInput) {
|
||||
try {
|
||||
await browser.runtime.sendMessage({id: 'startNativeApp'});
|
||||
} catch (err) {
|
||||
browser.runtime.sendMessage({
|
||||
id: 'notification',
|
||||
messageId: 'error_missingNativeApp'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await solve(simulateUserInput, ev);
|
||||
} finally {
|
||||
if (simulateUserInput) {
|
||||
await browser.runtime.sendMessage({id: 'stopNativeApp'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
const observer = new MutationObserver(syncUI);
|
||||
observer.observe(document.body, {
|
||||
|
27
src/storage/versions/local/t335iRDhZ8.js
Normal file
27
src/storage/versions/local/t335iRDhZ8.js
Normal file
@ -0,0 +1,27 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
|
||||
const message = 'Add simulateUserInput option';
|
||||
|
||||
const revision = 't335iRDhZ8';
|
||||
const downRevision = 'ZtLMLoh1ag';
|
||||
|
||||
const storage = browser.storage.local;
|
||||
|
||||
async function upgrade() {
|
||||
const changes = {
|
||||
simulateUserInput: false
|
||||
};
|
||||
|
||||
changes.storageVersion = revision;
|
||||
return storage.set(changes);
|
||||
}
|
||||
|
||||
async function downgrade() {
|
||||
const changes = {};
|
||||
await storage.remove(['simulateUserInput']);
|
||||
|
||||
changes.storageVersion = downRevision;
|
||||
return storage.set(changes);
|
||||
}
|
||||
|
||||
export {message, revision, upgrade, downgrade};
|
@ -4,6 +4,7 @@
|
||||
"ONiJBs00o",
|
||||
"UidMDYaYA",
|
||||
"nOedd0Txqd",
|
||||
"ZtLMLoh1ag"
|
||||
"ZtLMLoh1ag",
|
||||
"t335iRDhZ8"
|
||||
]
|
||||
}
|
||||
|
27
src/storage/versions/sync/t335iRDhZ8.js
Normal file
27
src/storage/versions/sync/t335iRDhZ8.js
Normal file
@ -0,0 +1,27 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
|
||||
const message = 'Add simulateUserInput option';
|
||||
|
||||
const revision = 't335iRDhZ8';
|
||||
const downRevision = 'ZtLMLoh1ag';
|
||||
|
||||
const storage = browser.storage.sync;
|
||||
|
||||
async function upgrade() {
|
||||
const changes = {
|
||||
simulateUserInput: false
|
||||
};
|
||||
|
||||
changes.storageVersion = revision;
|
||||
return storage.set(changes);
|
||||
}
|
||||
|
||||
async function downgrade() {
|
||||
const changes = {};
|
||||
await storage.remove(['simulateUserInput']);
|
||||
|
||||
changes.storageVersion = downRevision;
|
||||
return storage.set(changes);
|
||||
}
|
||||
|
||||
export {message, revision, upgrade, downgrade};
|
@ -4,6 +4,7 @@
|
||||
"ONiJBs00o",
|
||||
"UidMDYaYA",
|
||||
"nOedd0Txqd",
|
||||
"ZtLMLoh1ag"
|
||||
"ZtLMLoh1ag",
|
||||
"t335iRDhZ8"
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,13 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import uuidV4 from 'uuid/v4';
|
||||
|
||||
import {getText, createTab, getActiveTab} from 'utils/common';
|
||||
import {
|
||||
getText,
|
||||
createTab,
|
||||
getActiveTab,
|
||||
getRandomInt,
|
||||
sleep
|
||||
} from 'utils/common';
|
||||
|
||||
function showNotification({message, messageId, title, type = 'info'}) {
|
||||
if (!title) {
|
||||
@ -40,4 +47,58 @@ async function showContributePage(action = false) {
|
||||
await createTab(url, {index: activeTab.index + 1});
|
||||
}
|
||||
|
||||
export {showNotification, getOptionLabels, showContributePage};
|
||||
function meanSleep(ms) {
|
||||
const maxDeviation = (10 / 100) * ms;
|
||||
return sleep(getRandomInt(ms - maxDeviation, ms + maxDeviation));
|
||||
}
|
||||
|
||||
function sendNativeMessage(port, message, {timeout = 10000} = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = uuidV4();
|
||||
message.id = id;
|
||||
|
||||
const messageCallback = function(msg) {
|
||||
if (msg.id !== id) {
|
||||
return;
|
||||
}
|
||||
removeListeners();
|
||||
resolve(msg);
|
||||
};
|
||||
const errorCallback = function() {
|
||||
removeListeners();
|
||||
reject('No response from native app');
|
||||
};
|
||||
const removeListeners = function() {
|
||||
window.clearTimeout(timeoutId);
|
||||
port.onMessage.removeListener(messageCallback);
|
||||
port.onDisconnect.removeListener(errorCallback);
|
||||
};
|
||||
|
||||
const timeoutId = window.setTimeout(function() {
|
||||
errorCallback();
|
||||
}, timeout);
|
||||
|
||||
port.onMessage.addListener(messageCallback);
|
||||
port.onDisconnect.addListener(errorCallback);
|
||||
|
||||
port.postMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
async function pingClientApp() {
|
||||
await browser.runtime.sendMessage({id: 'startNativeApp'});
|
||||
await browser.runtime.sendMessage({
|
||||
id: 'sendNativeMessage',
|
||||
message: {command: 'ping'}
|
||||
});
|
||||
await browser.runtime.sendMessage({id: 'stopNativeApp'});
|
||||
}
|
||||
|
||||
export {
|
||||
showNotification,
|
||||
getOptionLabels,
|
||||
showContributePage,
|
||||
meanSleep,
|
||||
sendNativeMessage,
|
||||
pingClientApp
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import Bowser from 'bowser';
|
||||
|
||||
import {targetEnv} from 'utils/config';
|
||||
|
||||
@ -23,6 +24,40 @@ async function isAndroid() {
|
||||
return os === 'android';
|
||||
}
|
||||
|
||||
async function getPlatform() {
|
||||
let {os, arch} = await browser.runtime.getPlatformInfo();
|
||||
if (os === 'win') {
|
||||
os = 'windows';
|
||||
} else if (os === 'mac') {
|
||||
os = 'macos';
|
||||
}
|
||||
|
||||
if (arch === 'x86-32') {
|
||||
arch = '386';
|
||||
} else if (arch === 'x86-64') {
|
||||
arch = 'amd64';
|
||||
}
|
||||
|
||||
return {os, arch};
|
||||
}
|
||||
|
||||
async function getBrowser() {
|
||||
let name, version;
|
||||
try {
|
||||
({name, version} = await browser.runtime.getBrowserInfo());
|
||||
} catch (e) {}
|
||||
|
||||
if (!name) {
|
||||
({name, version} = Bowser.getParser(
|
||||
window.navigator.userAgent
|
||||
).getBrowser());
|
||||
}
|
||||
|
||||
name = name.toLowerCase();
|
||||
|
||||
return {name, version};
|
||||
}
|
||||
|
||||
async function getActiveTab() {
|
||||
const [tab] = await browser.tabs.query({
|
||||
lastFocusedWindow: true,
|
||||
@ -113,15 +148,32 @@ async function functionInContext(
|
||||
return isFunction;
|
||||
}
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function getRandomFloat(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => window.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export {
|
||||
getText,
|
||||
createTab,
|
||||
isAndroid,
|
||||
getPlatform,
|
||||
getBrowser,
|
||||
getActiveTab,
|
||||
waitForElement,
|
||||
arrayBufferToBase64,
|
||||
executeCode,
|
||||
executeFile,
|
||||
scriptsAllowed,
|
||||
functionInContext
|
||||
functionInContext,
|
||||
getRandomInt,
|
||||
getRandomFloat,
|
||||
sleep
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
const targetEnv = process.env.TARGET_ENV;
|
||||
|
||||
const clientAppApiVersion = '1';
|
||||
|
||||
const witApiKeys = {
|
||||
afrikaans: 'T3T7A2WS3TQJVBB4L4CTK2EEUI6N7YGZ',
|
||||
arabic: 'AD6RLFYBWRGGJD76SWKALZMUFVGMVCTB',
|
||||
@ -55,4 +57,4 @@ const witApiKeys = {
|
||||
zulu: 'B6OMGRZUYIJ5WLDQZODKCFCXCTH7PHB3'
|
||||
};
|
||||
|
||||
export {targetEnv, witApiKeys};
|
||||
export {targetEnv, clientAppApiVersion, witApiKeys};
|
||||
|
@ -7,9 +7,12 @@ const optionKeys = [
|
||||
'microsoftSpeechApiKey',
|
||||
'witSpeechApiKeys',
|
||||
'loadEnglishChallenge',
|
||||
'tryEnglishSpeechModel'
|
||||
'tryEnglishSpeechModel',
|
||||
'simulateUserInput'
|
||||
];
|
||||
|
||||
const clientAppPlatforms = ['windows/amd64', 'linux/amd64', 'macos/amd64'];
|
||||
|
||||
// https://developers.google.com/recaptcha/docs/language
|
||||
// https://cloud.google.com/speech-to-text/docs/languages
|
||||
const captchaGoogleSpeechApiLangCodes = {
|
||||
@ -341,6 +344,7 @@ const microsoftSpeechApiUrls = {
|
||||
|
||||
export {
|
||||
optionKeys,
|
||||
clientAppPlatforms,
|
||||
captchaGoogleSpeechApiLangCodes,
|
||||
captchaIbmSpeechApiLangCodes,
|
||||
captchaMicrosoftSpeechApiLangCodes,
|
||||
|
@ -29,7 +29,8 @@ module.exports = {
|
||||
background: './src/background/main.js',
|
||||
options: './src/options/main.js',
|
||||
contribute: './src/contribute/main.js',
|
||||
solve: './src/solve/main.js'
|
||||
solve: './src/solve/main.js',
|
||||
install: './src/install/main.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist', targetEnv, 'src'),
|
||||
@ -45,7 +46,7 @@ module.exports = {
|
||||
commonsUi: {
|
||||
name: 'commons-ui',
|
||||
chunks: chunk => {
|
||||
return ['options', 'contribute'].includes(chunk.name);
|
||||
return ['options', 'contribute', 'install'].includes(chunk.name);
|
||||
},
|
||||
minChunks: 2
|
||||
}
|
||||
|
@ -2353,6 +2353,11 @@ boom@2.x.x:
|
||||
dependencies:
|
||||
hoek "2.x.x"
|
||||
|
||||
bowser@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.1.0.tgz#76cc094f97578ba4858fb4359445ee1317d1be6f"
|
||||
integrity sha512-tP90ci4QY8PRBQjU0+iTsoO3DMNYtXCM0aVxeKhjxXF8IH9xTXUmjcTECPN+y5v0BGeRDfMcSLeohPiUZuz37g==
|
||||
|
||||
boxen@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
|
||||
|
Loading…
Reference in New Issue
Block a user