在hybrid_parsing下添加了update-cookie接口,添加了一个chrome扩展程序cookie-sniffer,现在只支持抖音的cookie抓取,如果出现更新会自动回调配置的webhook地址,动态更新update-cookie
This commit is contained in:
parent
8c98fb7032
commit
2db97eaee8
7 changed files with 918 additions and 0 deletions
|
|
@ -51,3 +51,59 @@ async def hybrid_parsing_single_video(request: Request,
|
||||||
params=dict(request.query_params),
|
params=dict(request.query_params),
|
||||||
)
|
)
|
||||||
raise HTTPException(status_code=status_code, detail=detail.dict())
|
raise HTTPException(status_code=status_code, detail=detail.dict())
|
||||||
|
|
||||||
|
# 更新Cookie
|
||||||
|
@router.post("/update_cookie",
|
||||||
|
response_model=ResponseModel,
|
||||||
|
summary="更新Cookie/Update Cookie")
|
||||||
|
async def update_cookie_api(request: Request,
|
||||||
|
service: str = Body(example="douyin", description="服务名称/Service name"),
|
||||||
|
cookie: str = Body(example="YOUR_NEW_COOKIE", description="新的Cookie值/New Cookie value")):
|
||||||
|
"""
|
||||||
|
# [中文]
|
||||||
|
### 用途:
|
||||||
|
- 更新指定服务的Cookie
|
||||||
|
### 参数:
|
||||||
|
- service: 服务名称 (如: douyin_web)
|
||||||
|
- cookie: 新的Cookie值
|
||||||
|
### 返回:
|
||||||
|
- 更新结果
|
||||||
|
|
||||||
|
# [English]
|
||||||
|
### Purpose:
|
||||||
|
- Update Cookie for specified service
|
||||||
|
### Parameters:
|
||||||
|
- service: Service name (e.g.: douyin_web)
|
||||||
|
- cookie: New Cookie value
|
||||||
|
### Return:
|
||||||
|
- Update result
|
||||||
|
|
||||||
|
# [示例/Example]
|
||||||
|
service = "douyin_web"
|
||||||
|
cookie = "YOUR_NEW_COOKIE"
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if service == "douyin":
|
||||||
|
from crawlers.douyin.web.web_crawler import DouyinWebCrawler
|
||||||
|
douyin_crawler = DouyinWebCrawler()
|
||||||
|
await douyin_crawler.update_cookie(cookie)
|
||||||
|
return ResponseModel(code=200,
|
||||||
|
router=request.url.path,
|
||||||
|
data={"message": f"Cookie for {service} updated successfully"})
|
||||||
|
elif service == "tiktok":
|
||||||
|
# 这里可以添加TikTok的cookie更新逻辑
|
||||||
|
# from crawlers.tiktok.web.web_crawler import TikTokWebCrawler
|
||||||
|
# tiktok_crawler = TikTokWebCrawler()
|
||||||
|
# await tiktok_crawler.update_cookie(cookie)
|
||||||
|
return ResponseModel(code=200,
|
||||||
|
router=request.url.path,
|
||||||
|
data={"message": f"Cookie for {service} will be updated (not implemented yet)"})
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Service '{service}' is not supported. Supported services: douyin, tiktok")
|
||||||
|
except Exception as e:
|
||||||
|
status_code = 400
|
||||||
|
detail = ErrorResponseModel(code=status_code,
|
||||||
|
router=request.url.path,
|
||||||
|
params=dict(request.query_params),
|
||||||
|
)
|
||||||
|
raise HTTPException(status_code=status_code, detail=detail.dict())
|
||||||
171
chrome-cookie-sniffer/README.md
Normal file
171
chrome-cookie-sniffer/README.md
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
# Chrome Cookie Sniffer
|
||||||
|
|
||||||
|
一个用于自动嗅探和提取网站Cookie的Chrome扩展程序。支持抖音等主流平台,具备智能去重、时间控制和Webhook回调等功能。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 🎯 **智能Cookie抓取** - 自动拦截POST/GET请求中的Cookie
|
||||||
|
- ⏱️ **防重复机制** - 5分钟内不重复抓取相同服务
|
||||||
|
- 🔄 **内容去重** - 只有Cookie内容变化时才保存
|
||||||
|
- 🎨 **现代化界面** - Card列表展示,状态一目了然
|
||||||
|
- 🔗 **Webhook回调** - Cookie更新时自动推送到指定地址
|
||||||
|
- 📋 **一键复制** - 快速复制Cookie到剪贴板
|
||||||
|
- 🗂️ **数据管理** - 支持导出、清理和单独删除
|
||||||
|
- 🔧 **调试友好** - 内置Webhook测试功能
|
||||||
|
|
||||||
|
## 支持的网站
|
||||||
|
|
||||||
|
- 🎵 **抖音** (douyin.com)
|
||||||
|
- 🚀 **扩展性** - 架构支持轻松添加更多平台
|
||||||
|
|
||||||
|
## 安装方法
|
||||||
|
|
||||||
|
### 1. 下载源码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
# 或直接下载ZIP文件并解压
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 在Chrome中加载扩展
|
||||||
|
|
||||||
|
1. **打开Chrome扩展管理页面**
|
||||||
|
- 方法一:地址栏输入 `chrome://extensions/`
|
||||||
|
- 方法二:菜单 → 更多工具 → 扩展程序
|
||||||
|
|
||||||
|
2. **启用开发者模式**
|
||||||
|
- 在扩展管理页面右上角,开启"开发者模式"开关
|
||||||
|
|
||||||
|
3. **加载解压的扩展程序**
|
||||||
|
- 点击"加载已解压的扩展程序"按钮
|
||||||
|
- 选择 `chrome-cookie-sniffer` 文件夹
|
||||||
|
- 确认加载
|
||||||
|
|
||||||
|
4. **验证安装**
|
||||||
|
- 扩展列表中出现"Cookie Sniffer"
|
||||||
|
- 浏览器工具栏出现扩展图标
|
||||||
|
- 状态显示为"已启用"
|
||||||
|
|
||||||
|
### 3. 权限确认
|
||||||
|
|
||||||
|
安装时Chrome会请求以下权限:
|
||||||
|
- `webRequest` - 拦截网络请求
|
||||||
|
- `storage` - 本地数据存储
|
||||||
|
- `cookies` - 读取Cookie信息
|
||||||
|
- `activeTab` - 当前标签页访问
|
||||||
|
- `host_permissions` - 访问douyin.com域名
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 基础使用
|
||||||
|
|
||||||
|
1. **访问目标网站** - 打开抖音等支持的网站
|
||||||
|
2. **触发请求** - 正常浏览,触发POST/GET请求
|
||||||
|
3. **查看结果** - 点击扩展图标查看抓取的Cookie
|
||||||
|
|
||||||
|
### 配置Webhook
|
||||||
|
|
||||||
|
1. **打开扩展弹窗**
|
||||||
|
2. **输入Webhook地址** - 在顶部输入框填入回调URL
|
||||||
|
3. **测试连接** - 点击"🔧 测试"按钮验证
|
||||||
|
4. **自动回调** - Cookie更新时自动POST到指定地址
|
||||||
|
|
||||||
|
### Webhook数据格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"service": "douyin",
|
||||||
|
"cookie": "具体的Cookie字符串",
|
||||||
|
"timestamp": "2025-08-29T12:34:56.789Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
测试时会额外包含:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"test": true,
|
||||||
|
"message": "这是一个测试回调..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据管理
|
||||||
|
|
||||||
|
- **📋 复制Cookie** - 点击卡片中的复制按钮
|
||||||
|
- **🗑️ 删除数据** - 删除单个服务的Cookie
|
||||||
|
- **🔄 刷新** - 手动刷新数据显示
|
||||||
|
- **📤 导出** - 导出所有数据为JSON文件
|
||||||
|
- **🧹 清空** - 清空所有Cookie数据
|
||||||
|
|
||||||
|
## 调试指南
|
||||||
|
|
||||||
|
### 查看日志
|
||||||
|
|
||||||
|
1. **打开扩展管理页面** (`chrome://extensions/`)
|
||||||
|
2. **找到Cookie Sniffer扩展**
|
||||||
|
3. **点击"服务工作进程"** - 查看蓝色链接
|
||||||
|
4. **查看控制台输出** - 所有日志都在这里
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
**Q: 扩展不工作?**
|
||||||
|
- 检查是否启用开发者模式
|
||||||
|
- 确认权限已正确授予
|
||||||
|
- 查看service worker是否正在运行
|
||||||
|
|
||||||
|
**Q: 没有抓取到Cookie?**
|
||||||
|
- 确认访问的是支持的网站
|
||||||
|
- 检查是否触发了POST/GET请求
|
||||||
|
- 查看service worker控制台日志
|
||||||
|
|
||||||
|
**Q: Webhook测试失败?**
|
||||||
|
- 检查URL格式是否正确
|
||||||
|
- 确认服务器支持跨域请求
|
||||||
|
- 验证服务器是否正常响应
|
||||||
|
|
||||||
|
### 开发者选项
|
||||||
|
|
||||||
|
修改 `background.js` 中的 `SERVICES` 配置来添加新网站:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const SERVICES = {
|
||||||
|
douyin: {
|
||||||
|
name: 'douyin',
|
||||||
|
displayName: '抖音',
|
||||||
|
domains: ['douyin.com'],
|
||||||
|
cookieDomain: '.douyin.com'
|
||||||
|
},
|
||||||
|
// 添加新服务
|
||||||
|
bilibili: {
|
||||||
|
name: 'bilibili',
|
||||||
|
displayName: 'B站',
|
||||||
|
domains: ['bilibili.com'],
|
||||||
|
cookieDomain: '.bilibili.com'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
chrome-cookie-sniffer/
|
||||||
|
├── manifest.json # 扩展配置文件
|
||||||
|
├── background.js # 后台服务脚本
|
||||||
|
├── popup.html # 弹窗界面
|
||||||
|
├── popup.js # 弹窗逻辑
|
||||||
|
└── README.md # 说明文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- ⚠️ **仅用于合法用途** - 请遵守网站服务条款
|
||||||
|
- 🔒 **数据安全** - Cookie数据存储在本地,不会上传
|
||||||
|
- 🔄 **定期更新** - 网站更新可能影响抓取效果
|
||||||
|
- 📱 **Chrome限制** - 部分网站可能有反爬虫机制
|
||||||
|
|
||||||
|
## 开源协议
|
||||||
|
|
||||||
|
本项目遵循 MIT 开源协议。
|
||||||
|
|
||||||
|
## 贡献指南
|
||||||
|
|
||||||
|
欢迎提交Issue和Pull Request来改进这个项目!
|
||||||
177
chrome-cookie-sniffer/background.js
Normal file
177
chrome-cookie-sniffer/background.js
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
// 启动时记录
|
||||||
|
console.log('Cookie Sniffer service worker 已启动');
|
||||||
|
|
||||||
|
// 服务配置
|
||||||
|
const SERVICES = {
|
||||||
|
douyin: {
|
||||||
|
name: 'douyin',
|
||||||
|
displayName: '抖音',
|
||||||
|
domains: ['douyin.com'],
|
||||||
|
cookieDomain: '.douyin.com'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取服务名称
|
||||||
|
function getServiceFromUrl(url) {
|
||||||
|
for (const [key, service] of Object.entries(SERVICES)) {
|
||||||
|
if (service.domains.some(domain => url.includes(domain))) {
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否在5分钟内抓取过
|
||||||
|
async function shouldSkipCapture(serviceName) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.local.get([`lastCapture_${serviceName}`], function(result) {
|
||||||
|
const lastTime = result[`lastCapture_${serviceName}`];
|
||||||
|
if (!lastTime) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const fiveMinutes = 5 * 60 * 1000;
|
||||||
|
const shouldSkip = (now - lastTime) < fiveMinutes;
|
||||||
|
|
||||||
|
if (shouldSkip) {
|
||||||
|
console.log(`${serviceName}: 5分钟内已抓取过,跳过`);
|
||||||
|
}
|
||||||
|
resolve(shouldSkip);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查Cookie是否有变化
|
||||||
|
async function isCookieChanged(serviceName, newCookie) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.storage.local.get([`cookieData_${serviceName}`], function(result) {
|
||||||
|
const existingData = result[`cookieData_${serviceName}`];
|
||||||
|
if (!existingData || existingData.cookie !== newCookie) {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
console.log(`${serviceName}: Cookie内容无变化,跳过`);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存Cookie数据
|
||||||
|
async function saveCookieData(serviceName, url, cookie, source = 'headers') {
|
||||||
|
const cookieData = {
|
||||||
|
service: serviceName,
|
||||||
|
url: url,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
lastUpdate: new Date().toISOString(),
|
||||||
|
cookie: cookie,
|
||||||
|
source: source
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存服务数据
|
||||||
|
chrome.storage.local.set({
|
||||||
|
[`cookieData_${serviceName}`]: cookieData,
|
||||||
|
[`lastCapture_${serviceName}`]: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
// 触发Webhook回调
|
||||||
|
await sendWebhook(serviceName, cookie);
|
||||||
|
|
||||||
|
console.log(`${serviceName}: Cookie已保存`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webhook回调
|
||||||
|
async function sendWebhook(serviceName, cookie) {
|
||||||
|
chrome.storage.local.get(['webhookUrl'], function(result) {
|
||||||
|
const webhookUrl = result.webhookUrl;
|
||||||
|
if (webhookUrl && webhookUrl.trim()) {
|
||||||
|
const payload = {
|
||||||
|
service: serviceName,
|
||||||
|
cookie: cookie,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(webhookUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
}).then(response => {
|
||||||
|
console.log(`Webhook回调成功: ${serviceName}`, response.status);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(`Webhook回调失败: ${serviceName}`, error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
async function(details) {
|
||||||
|
const service = getServiceFromUrl(details.url);
|
||||||
|
if (!service) return;
|
||||||
|
|
||||||
|
console.log(`请求拦截: ${service.displayName}`, details.url, details.method);
|
||||||
|
|
||||||
|
if (details.method === "POST" || details.method === "GET") {
|
||||||
|
// 检查5分钟限制
|
||||||
|
if (await shouldSkipCapture(service.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cookieFound = false;
|
||||||
|
|
||||||
|
// 尝试从请求头获取Cookie
|
||||||
|
if (details.requestHeaders) {
|
||||||
|
for (let header of details.requestHeaders) {
|
||||||
|
if (header.name.toLowerCase() === "cookie") {
|
||||||
|
console.log(`从请求头捕获到Cookie: ${service.displayName}`);
|
||||||
|
|
||||||
|
// 检查Cookie是否有变化
|
||||||
|
if (await isCookieChanged(service.name, header.value)) {
|
||||||
|
await saveCookieData(service.name, details.url, header.value, 'headers');
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果请求头没有Cookie,使用cookies API备用方案
|
||||||
|
if (!cookieFound) {
|
||||||
|
chrome.cookies.getAll({domain: service.cookieDomain}, async function(cookies) {
|
||||||
|
if (cookies && cookies.length > 0) {
|
||||||
|
console.log(`通过cookies API获取到: ${service.displayName}`, cookies.length, '个cookie');
|
||||||
|
const cookieString = cookies.map(c => `${c.name}=${c.value}`).join('; ');
|
||||||
|
|
||||||
|
// 检查Cookie是否有变化
|
||||||
|
if (await isCookieChanged(service.name, cookieString)) {
|
||||||
|
await saveCookieData(service.name, details.url, cookieString, 'cookies_api');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ urls: ["https://*.douyin.com/*", "https://douyin.com/*"] },
|
||||||
|
["requestHeaders", "extraHeaders"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 添加存储变化监听
|
||||||
|
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||||
|
if (areaName === 'local') {
|
||||||
|
// 监听服务数据变化
|
||||||
|
Object.keys(changes).forEach(key => {
|
||||||
|
if (key.startsWith('cookieData_')) {
|
||||||
|
const serviceName = key.replace('cookieData_', '');
|
||||||
|
const serviceConfig = SERVICES[serviceName];
|
||||||
|
if (serviceConfig && changes[key].newValue) {
|
||||||
|
console.log(`${serviceConfig.displayName} Cookie数据已更新`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
24
chrome-cookie-sniffer/manifest.json
Normal file
24
chrome-cookie-sniffer/manifest.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Cookie Sniffer",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "监听并获取指定网站的请求 Cookie",
|
||||||
|
"permissions": [
|
||||||
|
"webRequest",
|
||||||
|
"storage",
|
||||||
|
"activeTab",
|
||||||
|
"cookies"
|
||||||
|
],
|
||||||
|
"host_permissions": [
|
||||||
|
"https://*.douyin.com/*",
|
||||||
|
"https://douyin.com/*"
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_title": "Cookie Sniffer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
178
chrome-cookie-sniffer/popup.html
Normal file
178
chrome-cookie-sniffer/popup.html
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
width: 400px;
|
||||||
|
padding: 15px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
color: #2c3e50;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-config {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 1px solid #e1e8ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-config label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webhook-config input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
margin: 0 3px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary { background: #007bff; color: white; }
|
||||||
|
.btn-primary:hover { background: #0056b3; }
|
||||||
|
.btn-danger { background: #dc3545; color: white; }
|
||||||
|
.btn-danger:hover { background: #c82333; }
|
||||||
|
.btn-success { background: #28a745; color: white; }
|
||||||
|
.btn-success:hover { background: #1e7e34; }
|
||||||
|
|
||||||
|
.service-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border: 1px solid #e1e8ed;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-bottom: 1px solid #e1e8ed;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2c3e50;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-active { background: #28a745; }
|
||||||
|
.status-inactive { background: #6c757d; }
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-update {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
color: #6c757d;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-info {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-left: 4px solid #17a2b8;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h3>Cookie Sniffer</h3>
|
||||||
|
|
||||||
|
<div class="webhook-config">
|
||||||
|
<label>Webhook回调地址</label>
|
||||||
|
<div style="display: flex; gap: 8px; align-items: center;">
|
||||||
|
<input type="url" id="webhookUrl" placeholder="https://your-server.com/webhook" style="flex: 1;">
|
||||||
|
<button class="btn btn-sm" id="testWebhook" style="background: #17a2b8; color: white; white-space: nowrap;" disabled>
|
||||||
|
🔧 测试
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="webhookStatus" style="font-size: 11px; color: #666; margin-top: 4px; min-height: 14px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button class="btn btn-primary" id="refresh">刷新</button>
|
||||||
|
<button class="btn btn-danger" id="clear">清空所有</button>
|
||||||
|
<button class="btn btn-success" id="export">导出JSON</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="statusInfo" class="status-info" style="display: none;"></div>
|
||||||
|
|
||||||
|
<div id="serviceCards"></div>
|
||||||
|
|
||||||
|
<div id="emptyState" class="empty-state" style="display: none;">
|
||||||
|
暂未抓取到任何Cookie数据<br>
|
||||||
|
请访问相关网站触发请求
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
292
chrome-cookie-sniffer/popup.js
Normal file
292
chrome-cookie-sniffer/popup.js
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const refreshBtn = document.getElementById('refresh');
|
||||||
|
const clearBtn = document.getElementById('clear');
|
||||||
|
const exportBtn = document.getElementById('export');
|
||||||
|
const webhookInput = document.getElementById('webhookUrl');
|
||||||
|
const testWebhookBtn = document.getElementById('testWebhook');
|
||||||
|
const webhookStatus = document.getElementById('webhookStatus');
|
||||||
|
const statusInfo = document.getElementById('statusInfo');
|
||||||
|
const serviceCards = document.getElementById('serviceCards');
|
||||||
|
const emptyState = document.getElementById('emptyState');
|
||||||
|
|
||||||
|
// 服务配置
|
||||||
|
const SERVICES = {
|
||||||
|
douyin: { name: 'douyin', displayName: '抖音', icon: '🎵' }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载Webhook配置
|
||||||
|
function loadWebhookConfig() {
|
||||||
|
chrome.storage.local.get(['webhookUrl'], function(result) {
|
||||||
|
if (result.webhookUrl) {
|
||||||
|
webhookInput.value = result.webhookUrl;
|
||||||
|
}
|
||||||
|
updateTestButtonState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存Webhook配置
|
||||||
|
function saveWebhookConfig() {
|
||||||
|
const url = webhookInput.value.trim();
|
||||||
|
chrome.storage.local.set({ webhookUrl: url });
|
||||||
|
showStatusInfo('Webhook地址已保存');
|
||||||
|
updateTestButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新测试按钮状态
|
||||||
|
function updateTestButtonState() {
|
||||||
|
const url = webhookInput.value.trim();
|
||||||
|
testWebhookBtn.disabled = !url || !isValidUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证URL格式
|
||||||
|
function isValidUrl(string) {
|
||||||
|
try {
|
||||||
|
new URL(string);
|
||||||
|
return string.startsWith('http://') || string.startsWith('https://');
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试Webhook回调
|
||||||
|
async function testWebhook() {
|
||||||
|
const url = webhookInput.value.trim();
|
||||||
|
if (!url) {
|
||||||
|
webhookStatus.textContent = '请先输入Webhook地址';
|
||||||
|
webhookStatus.style.color = '#dc3545';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
testWebhookBtn.disabled = true;
|
||||||
|
testWebhookBtn.textContent = '⏳ 测试中...';
|
||||||
|
webhookStatus.textContent = '正在发送测试请求...';
|
||||||
|
webhookStatus.style.color = '#17a2b8';
|
||||||
|
|
||||||
|
// 获取现有数据或创建测试数据
|
||||||
|
chrome.storage.local.get(['cookieData_douyin'], async function(result) {
|
||||||
|
let testData;
|
||||||
|
|
||||||
|
if (result.cookieData_douyin) {
|
||||||
|
// 使用现有数据
|
||||||
|
testData = {
|
||||||
|
service: 'douyin',
|
||||||
|
cookie: result.cookieData_douyin.cookie,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
test: true,
|
||||||
|
message: '这是一个测试回调,使用了真实的Cookie数据'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 使用模拟数据
|
||||||
|
testData = {
|
||||||
|
service: 'douyin',
|
||||||
|
cookie: 'test_cookie=test_value; another_cookie=another_value',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
test: true,
|
||||||
|
message: '这是一个测试回调,使用了模拟Cookie数据'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(testData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
webhookStatus.textContent = `✅ 测试成功 (${response.status})`;
|
||||||
|
webhookStatus.style.color = '#28a745';
|
||||||
|
} else {
|
||||||
|
webhookStatus.textContent = `❌ 服务器错误 (${response.status})`;
|
||||||
|
webhookStatus.style.color = '#dc3545';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Webhook测试失败:', error);
|
||||||
|
if (error.name === 'TypeError' && error.message.includes('fetch')) {
|
||||||
|
webhookStatus.textContent = '❌ 网络错误或跨域限制';
|
||||||
|
} else {
|
||||||
|
webhookStatus.textContent = `❌ 请求失败: ${error.message}`;
|
||||||
|
}
|
||||||
|
webhookStatus.style.color = '#dc3545';
|
||||||
|
} finally {
|
||||||
|
testWebhookBtn.disabled = false;
|
||||||
|
testWebhookBtn.textContent = '🔧 测试';
|
||||||
|
updateTestButtonState();
|
||||||
|
|
||||||
|
// 5秒后清除状态信息
|
||||||
|
setTimeout(() => {
|
||||||
|
webhookStatus.textContent = '';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示状态信息
|
||||||
|
function showStatusInfo(message) {
|
||||||
|
statusInfo.textContent = message;
|
||||||
|
statusInfo.style.display = 'block';
|
||||||
|
setTimeout(() => {
|
||||||
|
statusInfo.style.display = 'none';
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载服务数据
|
||||||
|
function loadServiceData() {
|
||||||
|
const serviceKeys = Object.keys(SERVICES).map(service => `cookieData_${service}`);
|
||||||
|
chrome.storage.local.get(serviceKeys, function(result) {
|
||||||
|
const hasData = Object.keys(result).length > 0;
|
||||||
|
|
||||||
|
if (!hasData) {
|
||||||
|
serviceCards.innerHTML = '';
|
||||||
|
emptyState.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyState.style.display = 'none';
|
||||||
|
serviceCards.innerHTML = '';
|
||||||
|
|
||||||
|
Object.keys(SERVICES).forEach(serviceKey => {
|
||||||
|
const service = SERVICES[serviceKey];
|
||||||
|
const data = result[`cookieData_${serviceKey}`];
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
createServiceCard(service, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建服务卡片
|
||||||
|
function createServiceCard(service, data) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'service-card';
|
||||||
|
|
||||||
|
const isRecent = Date.now() - data.timestamp < 5 * 60 * 1000; // 5分钟内
|
||||||
|
const lastUpdate = new Date(data.lastUpdate).toLocaleString();
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="service-name">${service.icon} ${service.displayName}</div>
|
||||||
|
<div class="service-status ${isRecent ? 'status-active' : 'status-inactive'}">
|
||||||
|
${isRecent ? '活跃' : '休眠'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="last-update">上次更新: ${lastUpdate}</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-primary btn-sm copy-btn" data-service="${service.name}">
|
||||||
|
📋 复制Cookie
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger btn-sm delete-btn" data-service="${service.name}">
|
||||||
|
🗑️ 删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
serviceCards.appendChild(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制Cookie到剪贴板
|
||||||
|
async function copyCookie(serviceName) {
|
||||||
|
chrome.storage.local.get([`cookieData_${serviceName}`], async function(result) {
|
||||||
|
const data = result[`cookieData_${serviceName}`];
|
||||||
|
if (data && data.cookie) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(data.cookie);
|
||||||
|
showStatusInfo(`${SERVICES[serviceName].displayName} Cookie已复制到剪贴板`);
|
||||||
|
} catch (err) {
|
||||||
|
// 备用方案
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = data.cookie;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
showStatusInfo(`${SERVICES[serviceName].displayName} Cookie已复制到剪贴板`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除服务数据
|
||||||
|
function deleteService(serviceName) {
|
||||||
|
if (confirm(`确定要删除 ${SERVICES[serviceName].displayName} 的Cookie数据吗?`)) {
|
||||||
|
chrome.storage.local.remove([
|
||||||
|
`cookieData_${serviceName}`,
|
||||||
|
`lastCapture_${serviceName}`
|
||||||
|
], function() {
|
||||||
|
loadServiceData();
|
||||||
|
showStatusInfo(`${SERVICES[serviceName].displayName} 数据已删除`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空所有数据
|
||||||
|
function clearAllData() {
|
||||||
|
if (confirm('确定要清空所有Cookie数据吗?')) {
|
||||||
|
const keysToRemove = [];
|
||||||
|
Object.keys(SERVICES).forEach(service => {
|
||||||
|
keysToRemove.push(`cookieData_${service}`);
|
||||||
|
keysToRemove.push(`lastCapture_${service}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.storage.local.remove(keysToRemove, function() {
|
||||||
|
loadServiceData();
|
||||||
|
showStatusInfo('所有数据已清空');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出数据
|
||||||
|
function exportData() {
|
||||||
|
const serviceKeys = Object.keys(SERVICES).map(service => `cookieData_${service}`);
|
||||||
|
chrome.storage.local.get(serviceKeys, function(result) {
|
||||||
|
const exportData = {};
|
||||||
|
|
||||||
|
Object.keys(result).forEach(key => {
|
||||||
|
const serviceName = key.replace('cookieData_', '');
|
||||||
|
exportData[serviceName] = result[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([JSON.stringify(exportData, null, 2)], {type: 'application/json'});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `cookie-sniffer-${new Date().toISOString().slice(0,10)}.json`;
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
showStatusInfo('数据已导出');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件绑定
|
||||||
|
refreshBtn.addEventListener('click', loadServiceData);
|
||||||
|
clearBtn.addEventListener('click', clearAllData);
|
||||||
|
exportBtn.addEventListener('click', exportData);
|
||||||
|
webhookInput.addEventListener('blur', saveWebhookConfig);
|
||||||
|
webhookInput.addEventListener('input', updateTestButtonState);
|
||||||
|
testWebhookBtn.addEventListener('click', testWebhook);
|
||||||
|
|
||||||
|
// 代理点击事件
|
||||||
|
serviceCards.addEventListener('click', function(e) {
|
||||||
|
if (e.target.classList.contains('copy-btn')) {
|
||||||
|
const serviceName = e.target.getAttribute('data-service');
|
||||||
|
copyCookie(serviceName);
|
||||||
|
} else if (e.target.classList.contains('delete-btn')) {
|
||||||
|
const serviceName = e.target.getAttribute('data-service');
|
||||||
|
deleteService(serviceName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
loadWebhookConfig();
|
||||||
|
loadServiceData();
|
||||||
|
|
||||||
|
// 自动刷新(每30秒)
|
||||||
|
setInterval(loadServiceData, 30000);
|
||||||
|
});
|
||||||
|
|
@ -348,6 +348,26 @@ class DouyinWebCrawler:
|
||||||
# 对于URL列表
|
# 对于URL列表
|
||||||
return await WebCastIdFetcher.get_all_webcast_id(urls)
|
return await WebCastIdFetcher.get_all_webcast_id(urls)
|
||||||
|
|
||||||
|
async def update_cookie(self, cookie: str):
|
||||||
|
"""
|
||||||
|
更新指定服务的Cookie
|
||||||
|
|
||||||
|
Args:
|
||||||
|
service: 服务名称 (如: douyin_web)
|
||||||
|
cookie: 新的Cookie值
|
||||||
|
"""
|
||||||
|
global config
|
||||||
|
service = "douyin"
|
||||||
|
print('DouyinWebCrawler before update', config["TokenManager"][service]["headers"]["Cookie"])
|
||||||
|
print('DouyinWebCrawler to update', cookie)
|
||||||
|
# 1. 更新内存中的配置(立即生效)
|
||||||
|
config["TokenManager"][service]["headers"]["Cookie"] = cookie
|
||||||
|
print('DouyinWebCrawler cookie updated', config["TokenManager"][service]["headers"]["Cookie"])
|
||||||
|
# 2. 写入配置文件(持久化)
|
||||||
|
config_path = f"{path}/config.yaml"
|
||||||
|
with open(config_path, 'w', encoding='utf-8') as file:
|
||||||
|
yaml.dump(config, file, default_flow_style=False, allow_unicode=True, indent=2)
|
||||||
|
|
||||||
async def main(self):
|
async def main(self):
|
||||||
"""-------------------------------------------------------handler接口列表-------------------------------------------------------"""
|
"""-------------------------------------------------------handler接口列表-------------------------------------------------------"""
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue