389 lines
10 KiB
JavaScript
389 lines
10 KiB
JavaScript
// General Auto - Background Service Worker
|
|
console.log('General Auto 后台服务已启动');
|
|
|
|
// 任务队列和状态管理
|
|
const state = {
|
|
taskQueue: [],
|
|
isProcessing: false,
|
|
requestLogs: [],
|
|
maxLogs: 100
|
|
};
|
|
|
|
// 监听来自 content script 的消息
|
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
console.log('收到消息:', request);
|
|
|
|
switch (request.action) {
|
|
case 'executeTask':
|
|
executeTask(request.task, sender.tab.id)
|
|
.then(result => sendResponse({ success: true, data: result }))
|
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
return true;
|
|
|
|
case 'executeScript':
|
|
executeScript(request.code, sender.tab.id)
|
|
.then(result => sendResponse({ success: true, data: result }))
|
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
return true;
|
|
|
|
case 'getCookies':
|
|
getCookies(request.url)
|
|
.then(cookies => sendResponse({ success: true, cookies }))
|
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
return true;
|
|
|
|
case 'setCookies':
|
|
setCookies(request.cookies)
|
|
.then(() => sendResponse({ success: true }))
|
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
return true;
|
|
|
|
case 'clearCookies':
|
|
clearCookies(request.url)
|
|
.then(() => sendResponse({ success: true }))
|
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
return true;
|
|
|
|
case 'navigateTo':
|
|
navigateTo(request.url, sender.tab.id)
|
|
.then(() => sendResponse({ success: true }))
|
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
return true;
|
|
|
|
case 'getRequestLogs':
|
|
sendResponse({ success: true, logs: state.requestLogs });
|
|
break;
|
|
|
|
case 'clearRequestLogs':
|
|
state.requestLogs = [];
|
|
sendResponse({ success: true });
|
|
break;
|
|
|
|
case 'getStatus':
|
|
sendResponse({
|
|
success: true,
|
|
status: {
|
|
taskCount: state.taskQueue.length,
|
|
isProcessing: state.isProcessing,
|
|
requestLogCount: state.requestLogs.length
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'logRequest':
|
|
logRequest(request.data);
|
|
sendResponse({ success: true });
|
|
break;
|
|
|
|
default:
|
|
sendResponse({ success: false, error: '未知操作' });
|
|
}
|
|
});
|
|
|
|
// 执行自动化任务
|
|
async function executeTask(task, tabId) {
|
|
try {
|
|
const result = await chrome.scripting.executeScript({
|
|
target: { tabId: tabId },
|
|
func: executeTaskInPage,
|
|
args: [task]
|
|
});
|
|
return result[0].result;
|
|
} catch (error) {
|
|
console.error('执行任务失败:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 在页面中执行的任务函数
|
|
function executeTaskInPage(task) {
|
|
const { type, selector, action, value, timeout = 10000, waitVisible = true } = task;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => {
|
|
reject(new Error(`任务执行超时: ${timeout}ms`));
|
|
}, timeout);
|
|
|
|
try {
|
|
switch (type) {
|
|
case 'click':
|
|
waitForElement(selector, timeout, waitVisible).then(el => {
|
|
el.click();
|
|
clearTimeout(timer);
|
|
resolve({ success: true, message: '点击成功' });
|
|
}).catch(err => {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
});
|
|
break;
|
|
|
|
case 'input':
|
|
waitForElement(selector, timeout, waitVisible).then(el => {
|
|
el.value = value;
|
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
clearTimeout(timer);
|
|
resolve({ success: true, message: '输入成功' });
|
|
}).catch(err => {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
});
|
|
break;
|
|
|
|
case 'humanInput':
|
|
waitForElement(selector, timeout, waitVisible).then(el => {
|
|
humanTypeInput(el, value).then(() => {
|
|
clearTimeout(timer);
|
|
resolve({ success: true, message: '人类输入成功' });
|
|
});
|
|
}).catch(err => {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
});
|
|
break;
|
|
|
|
case 'getText':
|
|
waitForElement(selector, timeout, waitVisible).then(el => {
|
|
clearTimeout(timer);
|
|
resolve({ success: true, text: el.textContent.trim() });
|
|
}).catch(err => {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
});
|
|
break;
|
|
|
|
case 'getAttribute':
|
|
waitForElement(selector, timeout, waitVisible).then(el => {
|
|
const attrValue = el.getAttribute(value);
|
|
clearTimeout(timer);
|
|
resolve({ success: true, value: attrValue });
|
|
}).catch(err => {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
});
|
|
break;
|
|
|
|
case 'scroll':
|
|
window.scrollBy({
|
|
top: value.y || 0,
|
|
left: value.x || 0,
|
|
behavior: 'smooth'
|
|
});
|
|
clearTimeout(timer);
|
|
resolve({ success: true, message: '滚动成功' });
|
|
break;
|
|
|
|
case 'scrollToElement':
|
|
waitForElement(selector, timeout, false).then(el => {
|
|
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
clearTimeout(timer);
|
|
resolve({ success: true, message: '滚动到元素成功' });
|
|
}).catch(err => {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
});
|
|
break;
|
|
|
|
case 'wait':
|
|
setTimeout(() => {
|
|
clearTimeout(timer);
|
|
resolve({ success: true, message: `等待 ${value}ms 完成` });
|
|
}, value);
|
|
break;
|
|
|
|
case 'waitForElement':
|
|
waitForElement(selector, timeout, waitVisible).then(el => {
|
|
clearTimeout(timer);
|
|
resolve({ success: true, message: '元素已出现' });
|
|
}).catch(err => {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
});
|
|
break;
|
|
|
|
case 'evaluate':
|
|
try {
|
|
const result = eval(value);
|
|
clearTimeout(timer);
|
|
resolve({ success: true, result: result });
|
|
} catch (err) {
|
|
clearTimeout(timer);
|
|
reject(err);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
clearTimeout(timer);
|
|
reject(new Error(`未知任务类型: ${type}`));
|
|
}
|
|
} catch (error) {
|
|
clearTimeout(timer);
|
|
reject(error);
|
|
}
|
|
});
|
|
|
|
// 等待元素出现
|
|
function waitForElement(selector, timeout, checkVisible) {
|
|
return new Promise((resolve, reject) => {
|
|
const check = () => {
|
|
const element = document.querySelector(selector);
|
|
if (element) {
|
|
if (!checkVisible) {
|
|
resolve(element);
|
|
return true;
|
|
}
|
|
const rect = element.getBoundingClientRect();
|
|
const isVisible = rect.width > 0 && rect.height > 0 &&
|
|
window.getComputedStyle(element).visibility !== 'hidden' &&
|
|
window.getComputedStyle(element).display !== 'none';
|
|
if (isVisible) {
|
|
resolve(element);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (check()) return;
|
|
|
|
const observer = new MutationObserver(() => {
|
|
if (check()) {
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
|
|
observer.observe(document.body, {
|
|
childList: true,
|
|
subtree: true,
|
|
attributes: true,
|
|
attributeFilter: ['style', 'class']
|
|
});
|
|
|
|
setTimeout(() => {
|
|
observer.disconnect();
|
|
reject(new Error(`等待元素超时: ${selector}`));
|
|
}, timeout);
|
|
});
|
|
}
|
|
|
|
// 模拟人类输入
|
|
function humanTypeInput(element, text, minDelay = 50, maxDelay = 150) {
|
|
return new Promise((resolve) => {
|
|
let index = 0;
|
|
const type = () => {
|
|
if (index < text.length) {
|
|
element.value += text[index];
|
|
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
index++;
|
|
const delay = minDelay + Math.random() * (maxDelay - minDelay);
|
|
setTimeout(type, delay);
|
|
} else {
|
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
resolve();
|
|
}
|
|
};
|
|
type();
|
|
});
|
|
}
|
|
}
|
|
|
|
// 执行自定义脚本
|
|
async function executeScript(code, tabId) {
|
|
try {
|
|
const result = await chrome.scripting.executeScript({
|
|
target: { tabId: tabId },
|
|
func: (code) => eval(code),
|
|
args: [code]
|
|
});
|
|
return result[0].result;
|
|
} catch (error) {
|
|
console.error('执行脚本失败:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Cookie 管理
|
|
async function getCookies(url) {
|
|
if (url) {
|
|
return await chrome.cookies.getAll({ url });
|
|
} else {
|
|
return await chrome.cookies.getAll({});
|
|
}
|
|
}
|
|
|
|
async function setCookies(cookies) {
|
|
for (const cookie of cookies) {
|
|
try {
|
|
await chrome.cookies.set(cookie);
|
|
} catch (error) {
|
|
console.error('设置 Cookie 失败:', cookie, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function clearCookies(url) {
|
|
let cookies;
|
|
if (url) {
|
|
cookies = await chrome.cookies.getAll({ url });
|
|
} else {
|
|
cookies = await chrome.cookies.getAll({});
|
|
}
|
|
|
|
for (const cookie of cookies) {
|
|
const protocol = cookie.secure ? 'https:' : 'http:';
|
|
const cookieUrl = `${protocol}//${cookie.domain}${cookie.path}`;
|
|
await chrome.cookies.remove({
|
|
url: cookieUrl,
|
|
name: cookie.name
|
|
});
|
|
}
|
|
}
|
|
|
|
// 导航到指定 URL
|
|
async function navigateTo(url, tabId) {
|
|
await chrome.tabs.update(tabId, { url: url });
|
|
}
|
|
|
|
// 记录请求
|
|
function logRequest(data) {
|
|
state.requestLogs.push({
|
|
...data,
|
|
timestamp: Date.now()
|
|
});
|
|
|
|
// 限制日志数量
|
|
if (state.requestLogs.length > state.maxLogs) {
|
|
state.requestLogs.shift();
|
|
}
|
|
}
|
|
|
|
// 监听标签页更新
|
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
if (changeInfo.status === 'complete') {
|
|
console.log('页面加载完成:', tab.url);
|
|
}
|
|
});
|
|
|
|
// 监听网络请求
|
|
chrome.webRequest.onBeforeRequest.addListener(
|
|
(details) => {
|
|
// 可以在这里拦截和修改请求
|
|
if (details.type === 'xmlhttprequest' || details.type === 'fetch') {
|
|
console.log('API 请求:', details.url);
|
|
}
|
|
},
|
|
{ urls: ["<all_urls>"] },
|
|
[]
|
|
);
|
|
|
|
// 监听响应头
|
|
chrome.webRequest.onCompleted.addListener(
|
|
(details) => {
|
|
if (details.type === 'xmlhttprequest' || details.type === 'fetch') {
|
|
console.log('API 响应:', details.url, details.statusCode);
|
|
}
|
|
},
|
|
{ urls: ["<all_urls>"] },
|
|
[]
|
|
);
|