mirror of https://github.com/dessant/buster
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.
739 lines
19 KiB
JavaScript
739 lines
19 KiB
JavaScript
import audioBufferToWav from 'audiobuffer-to-wav';
|
|
import aes from 'crypto-js/aes';
|
|
import sha256 from 'crypto-js/sha256';
|
|
import utf8 from 'crypto-js/enc-utf8';
|
|
|
|
import {initStorage, migrateLegacyStorage} from 'storage/init';
|
|
import {isStorageReady} from 'storage/storage';
|
|
import storage from 'storage/storage';
|
|
import {
|
|
showNotification,
|
|
sendNativeMessage,
|
|
processMessageResponse,
|
|
processAppUse
|
|
} from 'utils/app';
|
|
import {
|
|
executeCode,
|
|
scriptsAllowed,
|
|
getBrowser,
|
|
getPlatform,
|
|
getRandomInt,
|
|
arrayBufferToBase64,
|
|
normalizeAudio,
|
|
sliceAudio
|
|
} from 'utils/common';
|
|
import {
|
|
recaptchaChallengeUrlRx,
|
|
captchaGoogleSpeechApiLangCodes,
|
|
captchaIbmSpeechApiLangCodes,
|
|
captchaMicrosoftSpeechApiLangCodes,
|
|
captchaWitSpeechApiLangCodes
|
|
} from 'utils/data';
|
|
import {targetEnv, clientAppVersion} from 'utils/config';
|
|
|
|
let nativePort;
|
|
let secrets;
|
|
|
|
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};
|
|
}
|
|
|
|
function initResetCaptcha() {
|
|
const initReset = function (challengeUrl) {
|
|
const script = document.createElement('script');
|
|
script.onload = function (ev) {
|
|
ev.target.remove();
|
|
document.dispatchEvent(
|
|
new CustomEvent('___resetCaptcha', {detail: challengeUrl})
|
|
);
|
|
};
|
|
script.src = chrome.runtime.getURL('/src/scripts/reset.js');
|
|
document.documentElement.appendChild(script);
|
|
};
|
|
|
|
const onMessage = function (request) {
|
|
if (request.id === 'resetCaptcha') {
|
|
removeCallbacks();
|
|
initReset(request.challengeUrl);
|
|
}
|
|
};
|
|
|
|
const removeCallbacks = function () {
|
|
window.clearTimeout(timeoutId);
|
|
chrome.runtime.onMessage.removeListener(onMessage);
|
|
};
|
|
|
|
const timeoutId = window.setTimeout(removeCallbacks, 10000); // 10 seconds
|
|
|
|
chrome.runtime.onMessage.addListener(onMessage);
|
|
}
|
|
|
|
async function resetCaptcha(tabId, frameId, challengeUrl) {
|
|
frameId = (await browser.webNavigation.getFrame({tabId, frameId}))
|
|
.parentFrameId;
|
|
|
|
if (!(await scriptsAllowed(tabId, frameId))) {
|
|
await showNotification({messageId: 'error_scriptsNotAllowed'});
|
|
return;
|
|
}
|
|
|
|
await executeCode(`(${initResetCaptcha.toString()})()`, tabId, frameId);
|
|
|
|
await browser.tabs.sendMessage(
|
|
tabId,
|
|
{
|
|
id: 'resetCaptcha',
|
|
challengeUrl
|
|
},
|
|
{frameId}
|
|
);
|
|
}
|
|
|
|
function challengeRequestCallback(details) {
|
|
const url = new URL(details.url);
|
|
if (url.searchParams.get('hl') !== 'en') {
|
|
url.searchParams.set('hl', 'en');
|
|
return {redirectUrl: url.toString()};
|
|
}
|
|
}
|
|
|
|
async function setChallengeLocale() {
|
|
const {loadEnglishChallenge, simulateUserInput} = await storage.get([
|
|
'loadEnglishChallenge',
|
|
'simulateUserInput'
|
|
]);
|
|
|
|
if (loadEnglishChallenge || simulateUserInput) {
|
|
if (
|
|
!browser.webRequest.onBeforeRequest.hasListener(challengeRequestCallback)
|
|
) {
|
|
browser.webRequest.onBeforeRequest.addListener(
|
|
challengeRequestCallback,
|
|
{
|
|
urls: [
|
|
'https://google.com/recaptcha/api2/anchor*',
|
|
'https://google.com/recaptcha/api2/bframe*',
|
|
'https://www.google.com/recaptcha/api2/anchor*',
|
|
'https://www.google.com/recaptcha/api2/bframe*',
|
|
'https://google.com/recaptcha/enterprise/anchor*',
|
|
'https://google.com/recaptcha/enterprise/bframe*',
|
|
'https://www.google.com/recaptcha/enterprise/anchor*',
|
|
'https://www.google.com/recaptcha/enterprise/bframe*',
|
|
'https://recaptcha.net/recaptcha/api2/anchor*',
|
|
'https://recaptcha.net/recaptcha/api2/bframe*',
|
|
'https://www.recaptcha.net/recaptcha/api2/anchor*',
|
|
'https://www.recaptcha.net/recaptcha/api2/bframe*',
|
|
'https://recaptcha.net/recaptcha/enterprise/anchor*',
|
|
'https://recaptcha.net/recaptcha/enterprise/bframe*',
|
|
'https://www.recaptcha.net/recaptcha/enterprise/anchor*',
|
|
'https://www.recaptcha.net/recaptcha/enterprise/bframe*'
|
|
],
|
|
types: ['sub_frame']
|
|
},
|
|
['blocking']
|
|
);
|
|
}
|
|
} else if (
|
|
browser.webRequest.onBeforeRequest.hasListener(challengeRequestCallback)
|
|
) {
|
|
browser.webRequest.onBeforeRequest.removeListener(challengeRequestCallback);
|
|
}
|
|
}
|
|
|
|
function removeRequestOrigin(details) {
|
|
const origin = window.location.origin;
|
|
const headers = details.requestHeaders;
|
|
for (const header of headers) {
|
|
if (header.name.toLowerCase() === 'origin' && header.value === origin) {
|
|
headers.splice(headers.indexOf(header), 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {requestHeaders: headers};
|
|
}
|
|
|
|
function addBackgroundRequestListener() {
|
|
if (
|
|
!browser.webRequest.onBeforeSendHeaders.hasListener(removeRequestOrigin)
|
|
) {
|
|
const urls = [
|
|
'https://google.com/*',
|
|
'https://www.google.com/*',
|
|
'https://recaptcha.net/*',
|
|
'https://www.recaptcha.net/*',
|
|
'https://api.wit.ai/*',
|
|
'https://speech.googleapis.com/*',
|
|
'https://*.speech-to-text.watson.cloud.ibm.com/*',
|
|
'https://*.stt.speech.microsoft.com/*'
|
|
];
|
|
|
|
const extraInfo = ['blocking', 'requestHeaders'];
|
|
if (
|
|
targetEnv !== 'firefox' &&
|
|
Object.values(browser.webRequest.OnBeforeSendHeadersOptions).includes(
|
|
'extraHeaders'
|
|
)
|
|
) {
|
|
extraInfo.push('extraHeaders');
|
|
}
|
|
|
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
|
removeRequestOrigin,
|
|
{
|
|
urls,
|
|
types: ['xmlhttprequest']
|
|
},
|
|
extraInfo
|
|
);
|
|
}
|
|
}
|
|
|
|
function removeBackgroundRequestListener() {
|
|
if (browser.webRequest.onBeforeSendHeaders.hasListener(removeRequestOrigin)) {
|
|
browser.webRequest.onBeforeSendHeaders.removeListener(removeRequestOrigin);
|
|
}
|
|
}
|
|
|
|
async function prepareAudio(audio) {
|
|
const audioBuffer = await normalizeAudio(audio);
|
|
|
|
const audioSlice = await sliceAudio({
|
|
audioBuffer,
|
|
start: 1.5,
|
|
end: audioBuffer.duration - 1.5
|
|
});
|
|
|
|
return audioBufferToWav(audioSlice);
|
|
}
|
|
|
|
async function loadSecrets() {
|
|
try {
|
|
const ciphertext = await (await fetch('/secrets.txt')).text();
|
|
|
|
const key = sha256(
|
|
(await (await fetch('/src/background/script.js')).text()) +
|
|
(await (await fetch('/src/solve/script.js')).text())
|
|
).toString();
|
|
|
|
secrets = JSON.parse(aes.decrypt(ciphertext, key).toString(utf8));
|
|
} catch (err) {
|
|
secrets = {};
|
|
const {speechService} = await storage.get('speechService');
|
|
if (speechService === 'witSpeechApiDemo') {
|
|
await storage.set({speechService: 'witSpeechApi'});
|
|
}
|
|
}
|
|
}
|
|
|
|
async function getWitSpeechApiKey(speechService, language) {
|
|
if (speechService === 'witSpeechApiDemo') {
|
|
if (!secrets) {
|
|
await loadSecrets();
|
|
}
|
|
const apiKeys = secrets.witApiKeys;
|
|
if (apiKeys) {
|
|
const apiKey = apiKeys[language];
|
|
if (Array.isArray(apiKey)) {
|
|
return apiKey[getRandomInt(1, apiKey.length) - 1];
|
|
}
|
|
return apiKey;
|
|
}
|
|
} else {
|
|
const {witSpeechApiKeys: apiKeys} = await storage.get('witSpeechApiKeys');
|
|
return apiKeys[language];
|
|
}
|
|
}
|
|
|
|
async function getWitSpeechApiResult(apiKey, audioContent) {
|
|
const result = {};
|
|
|
|
const rsp = await fetch('https://api.wit.ai/speech?v=20221114', {
|
|
mode: 'cors',
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: 'Bearer ' + apiKey
|
|
},
|
|
body: new Blob([audioContent], {type: 'audio/wav'})
|
|
});
|
|
|
|
if (rsp.status !== 200) {
|
|
if (rsp.status === 429) {
|
|
result.errorId = 'error_apiQuotaExceeded';
|
|
result.errorTimeout = 6000;
|
|
} else {
|
|
throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`);
|
|
}
|
|
} else {
|
|
const data = JSON.parse((await rsp.text()).split('\r\n').at(-1)).text;
|
|
if (data) {
|
|
result.text = data.trim();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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}/v1/recognize?model=${model}&profanity_filter=false`,
|
|
{
|
|
mode: 'cors',
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: 'Basic ' + window.btoa('apikey:' + apiKey),
|
|
'X-Watson-Learning-Opt-Out': 'true'
|
|
},
|
|
body: new Blob([audioContent], {type: 'audio/wav'})
|
|
}
|
|
);
|
|
|
|
if (rsp.status !== 200) {
|
|
throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`);
|
|
}
|
|
|
|
const results = (await rsp.json()).results;
|
|
if (results && results.length) {
|
|
return results[0].alternatives[0].transcript.trim();
|
|
}
|
|
}
|
|
|
|
async function getMicrosoftSpeechApiResult(
|
|
apiLocation,
|
|
apiKey,
|
|
audioContent,
|
|
language
|
|
) {
|
|
const rsp = await fetch(
|
|
`https://${apiLocation}.stt.speech.microsoft.com/speech/recognition/conversation/cognitiveservices/v1?language=${language}&format=detailed&profanity=raw`,
|
|
{
|
|
mode: 'cors',
|
|
method: 'POST',
|
|
headers: {
|
|
'Ocp-Apim-Subscription-Key': apiKey,
|
|
'Content-type': 'audio/wav; codec=audio/pcm; samplerate=16000'
|
|
},
|
|
body: new Blob([audioContent], {type: 'audio/wav'})
|
|
}
|
|
);
|
|
|
|
if (rsp.status !== 200) {
|
|
throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`);
|
|
}
|
|
|
|
const results = (await rsp.json()).NBest;
|
|
if (results) {
|
|
return results[0].Lexical.trim();
|
|
}
|
|
}
|
|
|
|
async function transcribeAudio(audioUrl, lang) {
|
|
let solution;
|
|
|
|
const audioRsp = await fetch(audioUrl);
|
|
const audioContent = await prepareAudio(await audioRsp.arrayBuffer());
|
|
|
|
const {speechService, tryEnglishSpeechModel} = await storage.get([
|
|
'speechService',
|
|
'tryEnglishSpeechModel'
|
|
]);
|
|
|
|
if (['witSpeechApiDemo', 'witSpeechApi'].includes(speechService)) {
|
|
const language = captchaWitSpeechApiLangCodes[lang] || 'english';
|
|
|
|
const apiKey = await getWitSpeechApiKey(speechService, language);
|
|
|
|
if (!apiKey) {
|
|
showNotification({messageId: 'error_missingApiKey'});
|
|
return;
|
|
}
|
|
|
|
const result = await getWitSpeechApiResult(apiKey, audioContent);
|
|
if (result.errorId) {
|
|
showNotification({
|
|
messageId: result.errorId,
|
|
timeout: result.errorTimeout
|
|
});
|
|
return;
|
|
}
|
|
solution = result.text;
|
|
|
|
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({
|
|
messageId: result.errorId,
|
|
timeout: result.errorTimeout
|
|
});
|
|
return;
|
|
}
|
|
solution = result.text;
|
|
}
|
|
} else if (speechService === 'googleSpeechApi') {
|
|
const {googleSpeechApiKey: apiKey} = await storage.get(
|
|
'googleSpeechApiKey'
|
|
);
|
|
|
|
if (!apiKey) {
|
|
showNotification({messageId: 'error_missingApiKey'});
|
|
return;
|
|
}
|
|
|
|
const language = captchaGoogleSpeechApiLangCodes[lang] || 'en-US';
|
|
|
|
solution = await getGoogleSpeechApiResult(
|
|
apiKey,
|
|
audioContent,
|
|
language,
|
|
tryEnglishSpeechModel
|
|
);
|
|
} else if (speechService === 'ibmSpeechApi') {
|
|
const {ibmSpeechApiUrl: apiUrl, ibmSpeechApiKey: apiKey} =
|
|
await storage.get(['ibmSpeechApiUrl', 'ibmSpeechApiKey']);
|
|
|
|
if (!apiUrl) {
|
|
showNotification({messageId: 'error_missingApiUrl'});
|
|
return;
|
|
}
|
|
if (!apiKey) {
|
|
showNotification({messageId: 'error_missingApiKey'});
|
|
return;
|
|
}
|
|
|
|
const model = captchaIbmSpeechApiLangCodes[lang] || 'en-US_Multimedia';
|
|
|
|
solution = await getIbmSpeechApiResult(apiUrl, apiKey, audioContent, model);
|
|
|
|
if (
|
|
!solution &&
|
|
!['en-US_Multimedia', 'en-GB_Multimedia'].includes(model) &&
|
|
tryEnglishSpeechModel
|
|
) {
|
|
solution = await getIbmSpeechApiResult(
|
|
apiUrl,
|
|
apiKey,
|
|
audioContent,
|
|
'en-US_Multimedia'
|
|
);
|
|
}
|
|
} else if (speechService === 'microsoftSpeechApi') {
|
|
const {microsoftSpeechApiLoc: apiLocaction, microsoftSpeechApiKey: apiKey} =
|
|
await storage.get(['microsoftSpeechApiLoc', 'microsoftSpeechApiKey']);
|
|
|
|
if (!apiKey) {
|
|
showNotification({messageId: 'error_missingApiKey'});
|
|
return;
|
|
}
|
|
|
|
const language = captchaMicrosoftSpeechApiLangCodes[lang] || 'en-US';
|
|
|
|
solution = await getMicrosoftSpeechApiResult(
|
|
apiLocaction,
|
|
apiKey,
|
|
audioContent,
|
|
language
|
|
);
|
|
if (
|
|
!solution &&
|
|
!['en-US', 'en-GB'].includes(language) &&
|
|
tryEnglishSpeechModel
|
|
) {
|
|
solution = await getMicrosoftSpeechApiResult(
|
|
apiLocaction,
|
|
apiKey,
|
|
audioContent,
|
|
'en-US'
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!solution) {
|
|
if (['witSpeechApiDemo', 'witSpeechApi'].includes(speechService)) {
|
|
showNotification({
|
|
messageId: 'error_captchaNotSolvedWitai',
|
|
timeout: 60000
|
|
});
|
|
} else {
|
|
showNotification({messageId: 'error_captchaNotSolved', timeout: 6000});
|
|
}
|
|
} else {
|
|
return solution;
|
|
}
|
|
}
|
|
|
|
async function processMessage(request, sender) {
|
|
if (request.id === 'notification') {
|
|
showNotification({
|
|
message: request.message,
|
|
messageId: request.messageId,
|
|
title: request.title,
|
|
type: request.type,
|
|
timeout: request.timeout
|
|
});
|
|
} else if (request.id === 'captchaSolved') {
|
|
await processAppUse();
|
|
} else if (request.id === 'transcribeAudio') {
|
|
addBackgroundRequestListener();
|
|
try {
|
|
return await transcribeAudio(request.audioUrl, request.lang);
|
|
} finally {
|
|
removeBackgroundRequestListener();
|
|
}
|
|
} else if (request.id === 'resetCaptcha') {
|
|
await resetCaptcha(sender.tab.id, sender.frameId, request.challengeUrl);
|
|
} else if (request.id === 'getFramePos') {
|
|
return getFramePos(sender.tab.id, sender.frameId, request.frameIndex);
|
|
} else if (request.id === 'getOsScale') {
|
|
let zoom = await browser.tabs.getZoom(sender.tab.id);
|
|
|
|
const [[scale, windowWidth]] = await browser.tabs.executeScript(
|
|
sender.tab.id,
|
|
{
|
|
code: `[window.devicePixelRatio, window.innerWidth];`,
|
|
runAt: 'document_start'
|
|
}
|
|
);
|
|
|
|
if (targetEnv === 'firefox') {
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1787649
|
|
|
|
function getImageElement(url) {
|
|
return new Promise(resolve => {
|
|
const img = new Image();
|
|
img.onload = () => {
|
|
resolve(img);
|
|
};
|
|
img.onerror = () => {
|
|
resolve();
|
|
};
|
|
img.onabort = () => {
|
|
resolve();
|
|
};
|
|
img.src = url;
|
|
});
|
|
}
|
|
|
|
const screenshotWidth = (
|
|
await getImageElement(
|
|
await browser.tabs.captureVisibleTab({
|
|
format: 'jpeg',
|
|
quality: 10
|
|
})
|
|
)
|
|
).naturalWidth;
|
|
|
|
if (Math.abs(screenshotWidth / windowWidth - scale * zoom) < 0.005) {
|
|
zoom = 1;
|
|
}
|
|
}
|
|
|
|
return scale / zoom;
|
|
} else if (request.id === 'startClientApp') {
|
|
nativePort = browser.runtime.connectNative('org.buster.client');
|
|
} else if (request.id === 'stopClientApp') {
|
|
if (nativePort) {
|
|
nativePort.disconnect();
|
|
}
|
|
} else if (request.id === 'messageClientApp') {
|
|
const message = {
|
|
apiVersion: clientAppVersion,
|
|
...request.message
|
|
};
|
|
return sendNativeMessage(nativePort, message);
|
|
} else if (request.id === 'openOptions') {
|
|
browser.runtime.openOptionsPage();
|
|
} else if (request.id === 'getPlatform') {
|
|
return getPlatform({fallback: false});
|
|
} else if (request.id === 'getBrowser') {
|
|
return getBrowser();
|
|
} else if (request.id === 'optionChange') {
|
|
await onOptionChange();
|
|
}
|
|
}
|
|
|
|
function onMessage(request, sender, sendResponse) {
|
|
const response = processMessage(request, sender);
|
|
|
|
return processMessageResponse(response, sendResponse);
|
|
}
|
|
|
|
async function onOptionChange() {
|
|
await setChallengeLocale();
|
|
}
|
|
|
|
async function onActionButtonClick(tab) {
|
|
await browser.runtime.openOptionsPage();
|
|
}
|
|
|
|
async function onInstall(details) {
|
|
if (
|
|
['chrome', 'edge', 'opera'].includes(targetEnv) &&
|
|
['install', 'update'].includes(details.reason)
|
|
) {
|
|
const tabs = await browser.tabs.query({
|
|
url: ['http://*/*', 'https://*/*'],
|
|
windowType: 'normal'
|
|
});
|
|
|
|
for (const tab of tabs) {
|
|
const tabId = tab.id;
|
|
|
|
const frames = await browser.webNavigation.getAllFrames({tabId});
|
|
for (const frame of frames) {
|
|
const frameId = frame.frameId;
|
|
|
|
if (frameId && recaptchaChallengeUrlRx.test(frame.url)) {
|
|
await browser.tabs.insertCSS(tabId, {
|
|
frameId,
|
|
runAt: 'document_idle',
|
|
file: '/src/solve/style.css'
|
|
});
|
|
|
|
await browser.tabs.executeScript(tabId, {
|
|
frameId,
|
|
runAt: 'document_idle',
|
|
file: '/src/solve/script.js'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const setupTabs = await browser.tabs.query({
|
|
url: 'http://127.0.0.1/buster/setup?session=*',
|
|
windowType: 'normal'
|
|
});
|
|
|
|
for (const tab of setupTabs) {
|
|
await browser.tabs.reload(tab.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
function addBrowserActionListener() {
|
|
browser.browserAction.onClicked.addListener(onActionButtonClick);
|
|
}
|
|
|
|
function addMessageListener() {
|
|
browser.runtime.onMessage.addListener(onMessage);
|
|
}
|
|
|
|
function addInstallListener() {
|
|
browser.runtime.onInstalled.addListener(onInstall);
|
|
}
|
|
|
|
async function setup() {
|
|
if (!(await isStorageReady())) {
|
|
await migrateLegacyStorage();
|
|
await initStorage();
|
|
}
|
|
|
|
await setChallengeLocale();
|
|
}
|
|
|
|
function init() {
|
|
addBrowserActionListener();
|
|
addMessageListener();
|
|
addInstallListener();
|
|
|
|
setup();
|
|
}
|
|
|
|
init();
|