新增通用的浏览器自动化扩展插件

This commit is contained in:
zyj
2025-11-21 14:09:59 +08:00
parent 87a077d40c
commit b448227d3e
17 changed files with 2156 additions and 2 deletions

View File

@@ -0,0 +1,93 @@
# General Auto 扩展安装说明
## 当前状态
扩展已简化,移除了图标依赖,应该可以正常加载。
## 手动安装测试步骤
1. **打开 Chrome 扩展页面**
- 在浏览器地址栏输入:`chrome://extensions/`
- 或者:菜单 → 更多工具 → 扩展程序
2. **启用开发者模式**
- 在右上角打开"开发者模式"开关
3. **加载扩展**
- 点击"加载已解压的扩展程序"
- 选择目录:`d:\Project\go-account-register\expand\general-auto`
4. **验证扩展**
- 应该看到扩展名称:**General Auto**
- 版本1.0.0
- 状态:已启用
5. **测试扩展是否工作**
- 打开任意网页(如 https://www.google.com
- 按 F12 打开控制台
- 输入:`console.log(window.GeneralAuto)`
- 应该看到扩展对象而不是 undefined
## 程序化加载问题排查
如果通过 Rod 加载仍然失败,可能的原因:
### 1. 路径问题
检查日志输出的"扩展目录"是否正确:
```
扩展目录: d:\Project\go-account-register\expand\general-auto
```
### 2. user-data-dir 冲突
临时用户数据目录可能会影响扩展加载。尝试使用固定目录。
### 3. Chrome 版本兼容性
Manifest V3 需要 Chrome 88+ 版本。
## 替代方案
如果自动加载仍有问题,可以使用以下方法:
### 方案 1: 使用固定的 user-data-dir
```go
userDataDir := "user-data/with-extension"
```
### 方案 2: 手动复制扩展到 user-data 目录
程序启动前,将扩展复制到用户数据目录的 Extensions 文件夹。
### 方案 3: 使用打包的扩展
```bash
# 在 general-auto 目录打包
chrome --pack-extension=d:\Project\go-account-register\expand\general-auto
```
然后使用 .crx 文件加载。
## 调试建议
在 chrome.go 中添加更多日志:
```go
log.Println("扩展目录:", expandPath)
log.Println("用户数据目录:", absPath)
log.Println("Chrome 路径:", path)
// 验证扩展目录是否存在
if _, err := os.Stat(expandPath); os.IsNotExist(err) {
log.Fatal("扩展目录不存在:", expandPath)
}
// 验证 manifest.json 是否存在
manifestPath := filepath.Join(expandPath, "manifest.json")
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
log.Fatal("manifest.json 不存在:", manifestPath)
}
```
## 当前 manifest.json 配置
已移除图标依赖,使用最小化配置:
- ✅ manifest_version: 3
- ✅ 基础权限tabs, cookies, storage, scripting
- ✅ background service worker
- ✅ content scripts
- ❌ 图标(暂时移除,不影响功能)

View File

@@ -0,0 +1,293 @@
# General Auto - 浏览器自动化控制扩展
## 简介
General Auto 是一个强大的 Chrome 浏览器自动化控制扩展,专为自动化测试和浏览器控制设计。
## 功能特性
### 核心功能
- ✅ 完整的页面元素操作(点击、输入、滚动等)
- ✅ Cookie 管理(获取、设置、清除)
- ✅ 请求拦截和日志记录XHR、Fetch
- ✅ 模拟人类行为(随机延迟、自然输入)
- ✅ 元素等待和检测
- ✅ 自定义脚本执行
- ✅ 任务队列管理
### API 功能
#### Content Script API (window.GeneralAuto)
```javascript
// 执行任务
await GeneralAuto.executeTask({
type: 'click',
selector: 'button#submit',
timeout: 5000
});
// 执行自定义脚本
await GeneralAuto.executeScript('document.title');
// Cookie 管理
const cookies = await GeneralAuto.getCookies();
await GeneralAuto.setCookies([...]);
await GeneralAuto.clearCookies();
// 导航
await GeneralAuto.navigateTo('https://example.com');
// 获取请求日志
const logs = await GeneralAuto.getRequestLogs();
```
#### Injected Script API (window.AutomationUtils)
```javascript
// 模拟人类输入
await AutomationUtils.humanInput(element, 'Hello World');
// 模拟人类点击
await AutomationUtils.humanClick(element);
// 等待元素
const el = await AutomationUtils.waitForElement('.selector', 5000);
// 等待元素消失
await AutomationUtils.waitForElementRemoved('.loading');
// 随机延迟
await AutomationUtils.randomDelay(100, 500);
// 监听请求
AutomationUtils.onRequest((data) => {
console.log('Request:', data);
});
// 监听响应
AutomationUtils.onResponse((data) => {
console.log('Response:', data);
});
```
## 任务类型
### 支持的任务类型
| 类型 | 说明 | 参数 |
|------|------|------|
| `click` | 点击元素 | `selector` |
| `input` | 输入文本 | `selector`, `value` |
| `humanInput` | 模拟人类输入 | `selector`, `value` |
| `getText` | 获取文本 | `selector` |
| `getAttribute` | 获取属性 | `selector`, `value`(属性名) |
| `scroll` | 滚动页面 | `value: {x, y}` |
| `scrollToElement` | 滚动到元素 | `selector` |
| `wait` | 等待指定时间 | `value`(毫秒) |
| `waitForElement` | 等待元素出现 | `selector` |
| `evaluate` | 执行 JavaScript | `value`(代码) |
### 任务示例
```javascript
// 点击按钮
await GeneralAuto.executeTask({
type: 'click',
selector: 'button[data-testid="submit"]',
timeout: 5000,
waitVisible: true
});
// 输入文本
await GeneralAuto.executeTask({
type: 'input',
selector: 'input[name="username"]',
value: 'myusername',
timeout: 5000
});
// 模拟人类输入
await GeneralAuto.executeTask({
type: 'humanInput',
selector: 'textarea',
value: 'This is a human-like typing',
timeout: 10000
});
// 获取文本
const result = await GeneralAuto.executeTask({
type: 'getText',
selector: '.title'
});
console.log(result.text);
// 滚动
await GeneralAuto.executeTask({
type: 'scroll',
value: { x: 0, y: 500 }
});
// 等待元素
await GeneralAuto.executeTask({
type: 'waitForElement',
selector: '.dynamic-content',
timeout: 10000
});
```
## 安装说明
### 1. 开发者模式安装
1. 打开 Chrome 浏览器
2. 访问 `chrome://extensions/`
3. 开启右上角的"开发者模式"
4. 点击"加载已解压的扩展程序"
5. 选择 `expand/general-auto` 文件夹
### 2. 在 Rod/Chromedp 中使用
```go
// 使用 Rod
browser := rod.New().
ControlURL(launcher.New().
Headless(false).
Set("load-extension", "d:\\Project\\go-account-register\\expand\\general-auto").
MustLaunch()).
MustConnect()
```
## 使用示例
### Go 代码中使用
```go
// 在浏览器启动时加载扩展
func OpenBrowserWithExtension() *rod.Browser {
extensionPath := "d:\\Project\\go-account-register\\expand\\general-auto"
browser := rod.New().
ControlURL(launcher.New().
Headless(false).
Set("load-extension", extensionPath).
MustLaunch()).
MustConnect()
return browser
}
// 在页面中执行自动化任务
func AutomateTwitterPost(page *rod.Page, content string) error {
// 等待扩展加载
time.Sleep(2 * time.Second)
// 执行任务
_, err := page.Eval(`
(async () => {
// 等待输入框
await GeneralAuto.executeTask({
type: 'waitForElement',
selector: '[data-testid="tweetTextarea_0"]',
timeout: 10000
});
// 输入内容
await GeneralAuto.executeTask({
type: 'humanInput',
selector: '[data-testid="tweetTextarea_0"]',
value: ` + "`" + content + "`" + `,
timeout: 15000
});
// 点击发布按钮
await GeneralAuto.executeTask({
type: 'click',
selector: '[data-testid="tweetButtonInline"]',
timeout: 5000
});
return true;
})()
`)
return err
}
```
### 页面控制台中使用
```javascript
// 1. 点击登录按钮
await GeneralAuto.executeTask({
type: 'click',
selector: 'button.login-btn'
});
// 2. 等待登录框出现
await GeneralAuto.executeTask({
type: 'waitForElement',
selector: '#login-modal'
});
// 3. 输入用户名
await GeneralAuto.executeTask({
type: 'humanInput',
selector: 'input[name="username"]',
value: 'myusername'
});
// 4. 输入密码
await GeneralAuto.executeTask({
type: 'humanInput',
selector: 'input[name="password"]',
value: 'mypassword'
});
// 5. 点击提交
await GeneralAuto.executeTask({
type: 'click',
selector: 'button[type="submit"]'
});
```
## 配置选项
### 请求拦截配置
```javascript
// 控制请求拦截
window.GeneralAutoConfig = {
interceptXHR: true, // 拦截 XHR 请求
interceptFetch: true, // 拦截 Fetch 请求
logRequests: true, // 记录请求日志
requestCallbacks: [], // 请求回调
responseCallbacks: [] // 响应回调
};
```
## 注意事项
1. 扩展需要在浏览器启动时加载
2. 某些网站可能检测自动化行为,建议使用人类模拟功能
3. 请求日志会占用内存,定期清理
4. 开发模式下扩展可能需要手动刷新
## 更新日志
### v1.0.0 (2025-01-21)
- ✨ 初始版本发布
- ✅ 实现基础自动化功能
- ✅ 添加 Cookie 管理
- ✅ 添加请求拦截
- ✅ 添加人类行为模拟
## 许可证
MIT License
## 支持
如有问题,请联系开发团队。

View File

@@ -0,0 +1,124 @@
# Chrome 扩展加载诊断
## 问题:扩展目录正确但未加载
### 已确认正确的信息:
- ✅ 扩展目录: D:\Project\go-account-register\expand\general-auto
- ✅ manifest.json 存在
- ✅ Chrome 路径正确
### 可能的原因和解决方案
#### 1. 用户数据目录冲突
**问题**: 每次使用随机临时目录可能导致扩展状态丢失
**解决方案**: 使用固定的用户数据目录测试
```go
userDataDir := "user-data/test-with-extension" // 固定目录
```
#### 2. Chrome 启动参数问题
某些参数组合可能导致扩展加载失败。
**测试步骤**:
1. 手动在命令行启动 Chrome 测试扩展加载:
```powershell
cd "C:\Program Files\Google\Chrome\Application"
.\chrome.exe --user-data-dir="D:\Project\go-account-register\user-data\test" --load-extension="D:\Project\go-account-register\expand\general-auto" --no-sandbox
```
2. 如果手动启动成功,说明是参数组合问题
#### 3. manifest.json 兼容性
**当前 manifest.json 可能的问题**:
- Service Worker 可能需要额外配置
- 权限设置可能有冲突
**测试方案**: 临时使用更简单的 manifest
```bash
# 重命名当前 manifest
cd D:\Project\go-account-register\expand\general-auto
rename manifest.json manifest-full.json
rename manifest-simple.json manifest.json
```
#### 4. 扩展 ID 和路径大小写
Windows 系统路径大小写敏感性问题
**验证**: 确保路径没有大小写混用
#### 5. Chrome 策略限制
企业版 Chrome 可能有扩展策略限制
**检查**:
1. 访问 `chrome://policy/`
2. 查看是否有 ExtensionInstallBlacklist 或类似策略
### 推荐的调试步骤
**步骤 1: 手动加载测试**
1. 打开 Chrome (不通过程序)
2. 访问 `chrome://extensions/`
3. 启用"开发者模式"
4. 点击"加载已解压的扩展程序"
5. 选择 `D:\Project\go-account-register\expand\general-auto`
6. 看是否有错误提示
**步骤 2: 使用简化版 manifest**
```powershell
cd D:\Project\go-account-register\expand\general-auto
Copy-Item manifest.json manifest-backup.json
Copy-Item manifest-simple.json manifest.json
```
然后重新运行程序
**步骤 3: 查看 Chrome 日志**
启动时添加日志参数:
```go
Set("enable-logging", "stderr").
Set("v", "1")
```
**步骤 4: 检查扩展文件权限**
确保所有文件可读:
```powershell
icacls "D:\Project\go-account-register\expand\general-auto" /t
```
### 临时解决方案
如果自动加载持续失败,可以:
1. **手动预加载扩展到用户数据目录**
```go
// 在程序启动前,将扩展复制到用户数据目录
extensionDir := filepath.Join(absPath, "Default", "Extensions", "general-auto")
os.MkdirAll(extensionDir, 0755)
// 复制扩展文件...
```
2. **使用打包的 .crx 文件**
```powershell
# 打包扩展
chrome --pack-extension="D:\Project\go-account-register\expand\general-auto"
```
3. **不使用扩展,直接注入脚本**
```go
page.MustEvalOnNewDocument(`
// 直接注入你的自动化代码
window.GeneralAuto = { ... };
`)
```
### 下一步行动
请尝试:
1. 运行程序后,手动访问 `chrome://extensions/` 截图
2. 尝试手动加载扩展,看是否有错误信息
3. 使用简化版 manifest 测试
如果还是不行,请提供:
- `chrome://extensions/` 的截图
- 控制台是否有任何错误信息
- 手动加载时的错误提示(如果有)

View File

@@ -0,0 +1,388 @@
// 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>"] },
[]
);

View File

@@ -0,0 +1,186 @@
// General Auto - Content Script
console.log('General Auto 内容脚本已加载');
// 注入自定义脚本到页面
const script = document.createElement('script');
script.src = chrome.runtime.getURL('injected.js');
script.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(script);
// 监听页面消息
window.addEventListener('message', function(event) {
if (event.source !== window) return;
if (event.data.type && event.data.type === 'FROM_PAGE') {
console.log('Content script 收到页面消息:', event.data);
// 转发到 background script
chrome.runtime.sendMessage({
action: event.data.action,
data: event.data.data
}, response => {
if (response) {
window.postMessage({
type: 'FROM_EXTENSION',
action: event.data.action,
requestId: event.data.requestId,
data: response
}, '*');
}
});
}
});
// 提供 API 供外部调用
window.GeneralAuto = {
version: '1.0.0',
// 执行任务
executeTask: async function(task) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'executeTask',
task: task
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve(response.data);
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
},
// 执行自定义脚本
executeScript: async function(code) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'executeScript',
code: code
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve(response.data);
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
},
// 获取 Cookies
getCookies: async function(url) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'getCookies',
url: url || window.location.href
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve(response.cookies);
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
},
// 设置 Cookies
setCookies: async function(cookies) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'setCookies',
cookies: cookies
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve();
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
},
// 清除 Cookies
clearCookies: async function(url) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'clearCookies',
url: url || window.location.href
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve();
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
},
// 导航到指定 URL
navigateTo: async function(url) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'navigateTo',
url: url
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve();
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
},
// 获取请求日志
getRequestLogs: async function() {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'getRequestLogs'
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve(response.logs);
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
},
// 清除请求日志
clearRequestLogs: async function() {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'clearRequestLogs'
}, response => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else if (response && response.success) {
resolve();
} else {
reject(new Error(response ? response.error : '未知错误'));
}
});
});
}
};
// 导出到全局
window.GA = window.GeneralAuto;
console.log('GeneralAuto API 已就绪');

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>生成图标</title>
</head>
<body>
<canvas id="canvas16" width="16" height="16"></canvas>
<canvas id="canvas48" width="48" height="48"></canvas>
<canvas id="canvas128" width="128" height="128"></canvas>
<script>
// 生成 16x16 图标
const canvas16 = document.getElementById('canvas16');
const ctx16 = canvas16.getContext('2d');
const gradient16 = ctx16.createLinearGradient(0, 0, 16, 16);
gradient16.addColorStop(0, '#667eea');
gradient16.addColorStop(1, '#764ba2');
ctx16.fillStyle = gradient16;
ctx16.fillRect(0, 0, 16, 16);
ctx16.fillStyle = 'white';
ctx16.font = 'bold 10px Arial';
ctx16.textAlign = 'center';
ctx16.textBaseline = 'middle';
ctx16.fillText('GA', 8, 8);
// 生成 48x48 图标
const canvas48 = document.getElementById('canvas48');
const ctx48 = canvas48.getContext('2d');
const gradient48 = ctx48.createLinearGradient(0, 0, 48, 48);
gradient48.addColorStop(0, '#667eea');
gradient48.addColorStop(1, '#764ba2');
ctx48.fillStyle = gradient48;
ctx48.fillRect(0, 0, 48, 48);
ctx48.fillStyle = 'white';
ctx48.font = 'bold 24px Arial';
ctx48.textAlign = 'center';
ctx48.textBaseline = 'middle';
ctx48.fillText('GA', 24, 24);
// 生成 128x128 图标
const canvas128 = document.getElementById('canvas128');
const ctx128 = canvas128.getContext('2d');
const gradient128 = ctx128.createLinearGradient(0, 0, 128, 128);
gradient128.addColorStop(0, '#667eea');
gradient128.addColorStop(1, '#764ba2');
ctx128.fillStyle = gradient128;
ctx128.fillRect(0, 0, 128, 128);
ctx128.fillStyle = 'white';
ctx128.font = 'bold 56px Arial';
ctx128.textAlign = 'center';
ctx128.textBaseline = 'middle';
ctx128.fillText('GA', 64, 64);
// 自动下载
setTimeout(() => {
['16', '48', '128'].forEach(size => {
const canvas = document.getElementById('canvas' + size);
canvas.toBlob(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'icon' + size + '.png';
a.click();
});
});
}, 100);
</script>
<p>图标生成中,请保存下载的 PNG 文件到 icons 目录...</p>
<p>或者手动右键点击下面的图标保存:</p>
<div>
<h3>16x16:</h3>
<canvas id="preview16" width="16" height="16"></canvas>
</div>
<div>
<h3>48x48:</h3>
<canvas id="preview48" width="48" height="48"></canvas>
</div>
<div>
<h3>128x128:</h3>
<canvas id="preview128" width="128" height="128"></canvas>
</div>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SVG to PNG Converter</title>
</head>
<body>
<h2>SVG 转 PNG 工具</h2>
<p>请按照以下步骤操作:</p>
<ol>
<li>在浏览器中打开 icon16.svg, icon48.svg, icon128.svg</li>
<li>右键点击 → 另存为</li>
<li>保存为 icon16.png, icon48.png, icon128.png</li>
</ol>
<h3>或者使用在线工具:</h3>
<ul>
<li><a href="https://cloudconvert.com/svg-to-png" target="_blank">CloudConvert</a></li>
<li><a href="https://www.aconvert.com/image/svg-to-png/" target="_blank">Aconvert</a></li>
<li><a href="https://svgtopng.com/" target="_blank">SVG to PNG</a></li>
</ul>
<h3>临时解决方案:</h3>
<p>Chrome 扩展也可以直接使用 SVG 文件,修改 manifest.json</p>
<pre>
"icons": {
"16": "icons/icon16.svg",
"48": "icons/icon48.svg",
"128": "icons/icon128.svg"
}
</pre>
</body>
</html>

View File

@@ -0,0 +1,13 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="128" height="128" rx="20" fill="url(#grad1)"/>
<text x="64" y="90" font-family="Arial, sans-serif" font-size="56" font-weight="bold" fill="white" text-anchor="middle">GA</text>
<circle cx="64" cy="40" r="3" fill="white" opacity="0.8"/>
<circle cx="90" cy="30" r="2" fill="white" opacity="0.6"/>
<circle cx="40" cy="35" r="2" fill="white" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 699 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="16" height="16" rx="3" fill="url(#grad1)"/>
<text x="8" y="12" font-family="Arial" font-size="10" font-weight="bold" fill="white" text-anchor="middle">GA</text>
</svg>

After

Width:  |  Height:  |  Size: 498 B

View File

@@ -0,0 +1,10 @@
<svg width="48" height="48" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="48" height="48" rx="8" fill="url(#grad1)"/>
<text x="24" y="34" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="white" text-anchor="middle">GA</text>
</svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@@ -0,0 +1,422 @@
// General Auto - Injected Script
(function() {
'use strict';
console.log('General Auto Injected script 已加载');
// 存储原始方法
const originalXHR = window.XMLHttpRequest;
const originalFetch = window.fetch;
const originalOpen = window.open;
// 请求拦截器配置
window.GeneralAutoConfig = {
interceptXHR: true,
interceptFetch: true,
logRequests: true,
requestCallbacks: [],
responseCallbacks: []
};
// 拦截 XMLHttpRequest
window.XMLHttpRequest = function() {
const xhr = new originalXHR();
const config = window.GeneralAutoConfig;
if (!config.interceptXHR) return xhr;
const originalOpen = xhr.open;
const originalSend = xhr.send;
const originalSetRequestHeader = xhr.setRequestHeader;
let requestData = {
method: '',
url: '',
headers: {},
body: null,
timestamp: Date.now()
};
xhr.open = function(method, url) {
requestData.method = method;
requestData.url = url;
return originalOpen.apply(this, arguments);
};
xhr.setRequestHeader = function(name, value) {
requestData.headers[name] = value;
return originalSetRequestHeader.apply(this, arguments);
};
xhr.send = function(body) {
requestData.body = body;
// 请求前回调
config.requestCallbacks.forEach(callback => {
try {
callback(requestData);
} catch (e) {
console.error('Request callback error:', e);
}
});
// 监听响应
this.addEventListener('load', function() {
const responseData = {
...requestData,
status: this.status,
statusText: this.statusText,
responseHeaders: this.getAllResponseHeaders(),
response: this.response,
responseText: this.responseText,
responseType: this.responseType,
responseURL: this.responseURL
};
// 响应后回调
config.responseCallbacks.forEach(callback => {
try {
callback(responseData);
} catch (e) {
console.error('Response callback error:', e);
}
});
// 发送到 background
if (config.logRequests) {
window.postMessage({
type: 'FROM_PAGE',
action: 'logRequest',
data: {
type: 'xhr',
method: responseData.method,
url: responseData.url,
status: responseData.status,
requestHeaders: responseData.headers,
requestBody: responseData.body,
responseHeaders: responseData.responseHeaders,
response: responseData.responseText
}
}, '*');
}
});
this.addEventListener('error', function() {
console.error('XHR Error:', requestData.url);
});
return originalSend.apply(this, arguments);
};
return xhr;
};
// 拦截 Fetch
window.fetch = function() {
const config = window.GeneralAutoConfig;
if (!config.interceptFetch) {
return originalFetch.apply(this, arguments);
}
const url = arguments[0];
const options = arguments[1] || {};
const requestData = {
method: options.method || 'GET',
url: typeof url === 'string' ? url : url.url,
headers: options.headers || {},
body: options.body,
timestamp: Date.now()
};
// 请求前回调
config.requestCallbacks.forEach(callback => {
try {
callback(requestData);
} catch (e) {
console.error('Request callback error:', e);
}
});
return originalFetch.apply(this, arguments).then(response => {
const clonedResponse = response.clone();
clonedResponse.text().then(body => {
const responseData = {
...requestData,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
response: body,
responseURL: response.url
};
// 响应后回调
config.responseCallbacks.forEach(callback => {
try {
callback(responseData);
} catch (e) {
console.error('Response callback error:', e);
}
});
// 发送到 background
if (config.logRequests) {
window.postMessage({
type: 'FROM_PAGE',
action: 'logRequest',
data: {
type: 'fetch',
method: responseData.method,
url: responseData.url,
status: responseData.status,
requestHeaders: responseData.headers,
requestBody: responseData.body,
responseHeaders: responseData.headers,
response: body
}
}, '*');
}
});
return response;
});
};
// 提供全局自动化工具 API
window.AutomationUtils = {
// 模拟人类输入
humanInput: function(element, text, minDelay = 50, maxDelay = 150) {
return new Promise((resolve) => {
element.focus();
let index = 0;
const type = () => {
if (index < text.length) {
const char = text[index];
// 触发键盘事件
const keydownEvent = new KeyboardEvent('keydown', {
key: char,
code: `Key${char.toUpperCase()}`,
bubbles: true,
cancelable: true
});
const keypressEvent = new KeyboardEvent('keypress', {
key: char,
code: `Key${char.toUpperCase()}`,
bubbles: true,
cancelable: true
});
const keyupEvent = new KeyboardEvent('keyup', {
key: char,
code: `Key${char.toUpperCase()}`,
bubbles: true,
cancelable: true
});
element.dispatchEvent(keydownEvent);
element.dispatchEvent(keypressEvent);
element.value += char;
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(keyupEvent);
index++;
const delay = minDelay + Math.random() * (maxDelay - minDelay);
setTimeout(type, delay);
} else {
element.dispatchEvent(new Event('change', { bubbles: true }));
element.blur();
resolve();
}
};
setTimeout(type, 100);
});
},
// 模拟人类点击
humanClick: function(element, options = {}) {
return new Promise((resolve) => {
const rect = element.getBoundingClientRect();
const x = rect.left + (options.offsetX || rect.width * (0.3 + Math.random() * 0.4));
const y = rect.top + (options.offsetY || rect.height * (0.3 + Math.random() * 0.4));
// 滚动到元素
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
setTimeout(() => {
// 鼠标移动
const mousemoveEvent = new MouseEvent('mousemove', {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y
});
element.dispatchEvent(mousemoveEvent);
setTimeout(() => {
// 鼠标按下
const mousedownEvent = new MouseEvent('mousedown', {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
button: 0
});
element.dispatchEvent(mousedownEvent);
setTimeout(() => {
// 鼠标抬起
const mouseupEvent = new MouseEvent('mouseup', {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
button: 0
});
element.dispatchEvent(mouseupEvent);
// 点击
const clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
button: 0
});
element.dispatchEvent(clickEvent);
resolve();
}, 50 + Math.random() * 50);
}, 50 + Math.random() * 50);
}, 200 + Math.random() * 300);
});
},
// 等待元素出现
waitForElement: function(selector, timeout = 10000, checkVisible = true) {
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);
});
},
// 等待元素消失
waitForElementRemoved: function(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const check = () => {
const element = document.querySelector(selector);
if (!element) {
resolve();
return true;
}
const rect = element.getBoundingClientRect();
const isHidden = rect.width === 0 || rect.height === 0 ||
window.getComputedStyle(element).visibility === 'hidden' ||
window.getComputedStyle(element).display === 'none';
if (isHidden) {
resolve();
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);
});
},
// 随机延迟
randomDelay: function(min = 100, max = 500) {
return new Promise(resolve => {
setTimeout(resolve, min + Math.random() * (max - min));
});
},
// 获取所有元素
getElements: function(selector) {
return Array.from(document.querySelectorAll(selector));
},
// 监听请求
onRequest: function(callback) {
window.GeneralAutoConfig.requestCallbacks.push(callback);
},
// 监听响应
onResponse: function(callback) {
window.GeneralAutoConfig.responseCallbacks.push(callback);
},
// 清除所有回调
clearCallbacks: function() {
window.GeneralAutoConfig.requestCallbacks = [];
window.GeneralAutoConfig.responseCallbacks = [];
}
};
// 简写
window.AU = window.AutomationUtils;
console.log('AutomationUtils API 已就绪');
})();

View File

@@ -0,0 +1,39 @@
{
"manifest_version": 3,
"name": "General Auto - Browser Automation Controller",
"version": "1.0.0",
"description": "通用浏览器自动化控制扩展",
"permissions": [
"tabs",
"webRequest",
"webNavigation",
"cookies",
"storage",
"debugger",
"scripting"
],
"host_permissions": [
"http://*/*",
"https://*/*"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start",
"all_frames": true
}
],
"web_accessible_resources": [
{
"resources": ["injected.js"],
"matches": ["<all_urls>"]
}
],
"action": {
"default_popup": "popup.html"
}
}

View File

@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>General Auto - 浏览器自动化控制器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 350px;
min-height: 400px;
padding: 15px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f5f5f5;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.header h2 {
font-size: 18px;
margin-bottom: 5px;
}
.header .version {
font-size: 12px;
opacity: 0.9;
}
.status-card {
background: white;
padding: 12px;
border-radius: 8px;
margin-bottom: 10px;
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
}
.status-card h3 {
font-size: 14px;
color: #333;
margin-bottom: 8px;
display: flex;
align-items: center;
}
.status-card h3::before {
content: '';
width: 3px;
height: 14px;
background: #667eea;
margin-right: 8px;
border-radius: 2px;
}
.status-item {
display: flex;
justify-content: space-between;
padding: 6px 0;
font-size: 13px;
border-bottom: 1px solid #f0f0f0;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
color: #666;
}
.status-value {
color: #333;
font-weight: 500;
}
.status-value.active {
color: #52c41a;
}
.status-value.inactive {
color: #999;
}
.button-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 10px;
}
button {
padding: 10px 15px;
background: #667eea;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.3s;
box-shadow: 0 2px 4px rgba(102, 126, 234, 0.2);
}
button:hover {
background: #5568d3;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
}
button:active {
transform: translateY(0);
}
button.secondary {
background: #95de64;
}
button.secondary:hover {
background: #7ec050;
}
button.danger {
background: #ff4d4f;
}
button.danger:hover {
background: #ff1f21;
}
button.full-width {
grid-column: 1 / -1;
}
.log-container {
background: white;
border-radius: 8px;
padding: 10px;
margin-top: 10px;
box-shadow: 0 1px 4px rgba(0,0,0,0.05);
}
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.log-header h3 {
font-size: 14px;
color: #333;
}
.clear-log {
padding: 4px 10px;
font-size: 12px;
background: #ff4d4f;
}
.log-content {
max-height: 150px;
overflow-y: auto;
font-size: 12px;
background: #fafafa;
padding: 8px;
border-radius: 4px;
font-family: 'Courier New', monospace;
line-height: 1.6;
}
.log-entry {
padding: 4px 0;
border-bottom: 1px solid #f0f0f0;
color: #666;
}
.log-entry:last-child {
border-bottom: none;
}
.log-time {
color: #999;
margin-right: 8px;
}
.log-message {
color: #333;
}
.log-content::-webkit-scrollbar {
width: 6px;
}
.log-content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.log-content::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.log-content::-webkit-scrollbar-thumb:hover {
background: #555;
}
.empty-log {
text-align: center;
color: #999;
padding: 20px;
}
.footer {
text-align: center;
font-size: 11px;
color: #999;
margin-top: 15px;
padding-top: 10px;
border-top: 1px solid #e0e0e0;
}
</style>
</head>
<body>
<div class="header">
<h2>General Auto</h2>
<div class="version">浏览器自动化控制器 v1.0.0</div>
</div>
<div class="status-card">
<h3>运行状态</h3>
<div class="status-item">
<span class="status-label">状态:</span>
<span class="status-value" id="status">就绪</span>
</div>
<div class="status-item">
<span class="status-label">任务队列:</span>
<span class="status-value" id="taskCount">0</span>
</div>
<div class="status-item">
<span class="status-label">请求日志:</span>
<span class="status-value" id="requestLogCount">0</span>
</div>
</div>
<div class="button-group">
<button id="refreshStatus">刷新状态</button>
<button id="getCookies" class="secondary">获取Cookie</button>
<button id="clearCookies" class="danger">清除Cookie</button>
<button id="viewLogs" class="secondary">查看日志</button>
</div>
<div class="log-container">
<div class="log-header">
<h3>操作日志</h3>
<button class="clear-log" id="clearLog">清空</button>
</div>
<div class="log-content" id="logContent">
<div class="empty-log">暂无日志</div>
</div>
</div>
<div class="footer">
© 2025 General Auto | 浏览器自动化工具
</div>
<script src="popup.js"></script>
</body>
</html>

View File

@@ -0,0 +1,153 @@
// Popup Script
document.addEventListener('DOMContentLoaded', function() {
const statusEl = document.getElementById('status');
const taskCountEl = document.getElementById('taskCount');
const requestLogCountEl = document.getElementById('requestLogCount');
const logContentEl = document.getElementById('logContent');
let logs = [];
// 添加日志
function addLog(message, type = 'info') {
const now = new Date();
const timeStr = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
logs.push({
time: timeStr,
message: message,
type: type
});
// 限制日志数量
if (logs.length > 50) {
logs.shift();
}
renderLogs();
}
// 渲染日志
function renderLogs() {
if (logs.length === 0) {
logContentEl.innerHTML = '<div class="empty-log">暂无日志</div>';
return;
}
logContentEl.innerHTML = logs.map(log => `
<div class="log-entry">
<span class="log-time">[${log.time}]</span>
<span class="log-message">${log.message}</span>
</div>
`).join('');
logContentEl.scrollTop = logContentEl.scrollHeight;
}
// 更新状态
function updateStatus() {
chrome.runtime.sendMessage({ action: 'getStatus' }, response => {
if (response && response.success) {
const status = response.status;
statusEl.textContent = status.isProcessing ? '运行中' : '就绪';
statusEl.className = 'status-value ' + (status.isProcessing ? 'active' : 'inactive');
taskCountEl.textContent = status.taskCount;
requestLogCountEl.textContent = status.requestLogCount;
addLog('状态已更新');
} else {
addLog('获取状态失败', 'error');
}
});
}
// 刷新状态按钮
document.getElementById('refreshStatus').addEventListener('click', function() {
updateStatus();
});
// 获取 Cookies 按钮
document.getElementById('getCookies').addEventListener('click', function() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
if (tabs[0]) {
chrome.runtime.sendMessage({
action: 'getCookies',
url: tabs[0].url
}, response => {
if (response && response.success) {
const cookieCount = response.cookies.length;
addLog(`获取到 ${cookieCount} 个 Cookie`);
console.log('Cookies:', response.cookies);
// 复制到剪贴板
const cookieText = JSON.stringify(response.cookies, null, 2);
navigator.clipboard.writeText(cookieText).then(() => {
addLog('Cookie 已复制到剪贴板');
}).catch(err => {
console.error('复制失败:', err);
});
} else {
addLog('获取 Cookie 失败', 'error');
}
});
}
});
});
// 清除 Cookies 按钮
document.getElementById('clearCookies').addEventListener('click', function() {
if (confirm('确定要清除当前页面的所有 Cookies 吗?')) {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
if (tabs[0]) {
chrome.runtime.sendMessage({
action: 'clearCookies',
url: tabs[0].url
}, response => {
if (response && response.success) {
addLog('Cookies 已清除');
} else {
addLog('清除 Cookies 失败', 'error');
}
});
}
});
}
});
// 查看日志按钮
document.getElementById('viewLogs').addEventListener('click', function() {
chrome.runtime.sendMessage({ action: 'getRequestLogs' }, response => {
if (response && response.success) {
const logCount = response.logs.length;
addLog(`获取到 ${logCount} 条请求日志`);
console.log('请求日志:', response.logs);
if (logCount > 0) {
// 显示最近 5 条
response.logs.slice(-5).forEach(log => {
addLog(`${log.method} ${log.url} - ${log.status}`);
});
}
} else {
addLog('获取请求日志失败', 'error');
}
});
});
// 清空日志按钮
document.getElementById('clearLog').addEventListener('click', function() {
logs = [];
renderLogs();
addLog('日志已清空');
});
// 初始化
addLog('扩展已就绪');
updateStatus();
// 定时更新状态
setInterval(updateStatus, 5000);
});