项目整理:添加Cookie Cloud下载功能、优化前端界面、配置改为config.json、打包脚本、清理无用文件

This commit is contained in:
bwstudio
2026-04-18 21:48:29 +08:00
parent 70802b9b50
commit be1709b622
14 changed files with 373 additions and 395 deletions
+1 -1
View File
@@ -49,4 +49,4 @@ def create_app(config_class=Config):
if __name__ == '__main__': if __name__ == '__main__':
app = create_app() app = create_app()
app.run(host='0.0.0.0', port=5000, debug=True) app.run(host='0.0.0.0', port=5000, debug=False)
Binary file not shown.
+19 -2
View File
@@ -13,17 +13,34 @@ website_bp = Blueprint('websites', __name__)
@website_bp.route('/check-network', methods=['GET']) @website_bp.route('/check-network', methods=['GET'])
def check_all_network(): def check_all_network():
"""检测所有网站的网络可达性""" """检测所有网站的网络可达性"""
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
websites = Website.query.all() websites = Website.query.all()
result = {} result = {}
for website in websites: for website in websites:
try: try:
resp = http_requests.get(website.url, timeout=5, allow_redirects=True) resp = http_requests.get(website.url, timeout=5, allow_redirects=True, verify=False)
result[website.id] = { result[website.id] = {
'status': resp.status_code, 'status': resp.status_code,
'reachable': resp.status_code < 400, 'reachable': resp.status_code < 500,
'response_time': resp.elapsed.total_seconds() * 1000 'response_time': resp.elapsed.total_seconds() * 1000
} }
except http_requests.exceptions.SSLError:
try:
resp = http_requests.get(website.url, timeout=5, allow_redirects=True, verify=False)
result[website.id] = {
'status': resp.status_code,
'reachable': resp.status_code < 500,
'response_time': resp.elapsed.total_seconds() * 1000
}
except Exception:
result[website.id] = {
'status': 0,
'reachable': False,
'response_time': 0
}
except Exception: except Exception:
result[website.id] = { result[website.id] = {
'status': 0, 'status': 0,
+37
View File
@@ -73,6 +73,18 @@ body {
background-clip: text; background-clip: text;
} }
.header-stat {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
font-weight: 500;
}
.header-stat b {
color: #fff;
font-size: 18px;
margin-left: 4px;
}
.header-right { .header-right {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -465,11 +477,36 @@ body {
font-size: 14px; font-size: 14px;
} }
/* 仪表板页脚 */
.dashboard-footer {
margin-top: 40px;
padding: 16px 20px;
background: var(--card-bg);
border-radius: var(--radius);
border: 1px solid var(--border-color);
box-shadow: var(--shadow-sm);
}
.dashboard-footer p {
margin: 0;
color: var(--text-secondary);
font-size: 13px;
text-align: center;
line-height: 1.6;
}
/* 统计数字卡片颜色 */ /* 统计数字卡片颜色 */
.stat-total .el-statistic__content { .stat-total .el-statistic__content {
color: var(--primary-color); color: var(--primary-color);
} }
.refresh-hint {
font-size: 12px;
color: var(--text-light);
font-weight: 400;
margin-left: 4px;
}
.stat-online .el-statistic__content { .stat-online .el-statistic__content {
color: var(--success-color); color: var(--success-color);
} }
+7 -1
View File
@@ -66,6 +66,12 @@ createApp({
} }
}; };
const handleDropdownCommand = async (command) => {
if (command === 'login') {
showAdminLogin();
}
};
const showAdminLogin = () => { const showAdminLogin = () => {
adminLoginForm.value = { password: '' }; adminLoginForm.value = { password: '' };
adminLoginVisible.value = true; adminLoginVisible.value = true;
@@ -579,7 +585,7 @@ createApp({
cookieDialogVisible, cookieDialogTitle, cookieForm, cookieDialogVisible, cookieDialogTitle, cookieForm,
websiteForm, filterWebsiteUser, websiteForm, filterWebsiteUser,
currentUserWebsites, filteredWebsites, currentUserWebsites, filteredWebsites,
checkAdminStatus, showAdminLogin, adminLogin, handleAdminCommand, checkAdminStatus, showAdminLogin, adminLogin, handleAdminCommand, handleDropdownCommand,
loadUsers, loadUsersWithWebsites, loadWebsites, loadDashboard, refreshDashboardStatus, loadUsers, loadUsersWithWebsites, loadWebsites, loadDashboard, refreshDashboardStatus,
startAutoRefresh, stopAutoRefresh, startAutoRefresh, stopAutoRefresh,
checkLogin, manageWebsite, manageUserWebsites, checkLogin, manageWebsite, manageUserWebsites,
+23 -16
View File
@@ -16,7 +16,7 @@
<el-container> <el-container>
<el-header> <el-header>
<div class="header-left"> <div class="header-left">
<h1>Cookie监控管理系统</h1> <h1>网站监控仪表板</h1>
<el-button v-if="activeTab === 'settings'" @click="activeTab = 'dashboard'" type="primary" plain>返回监控首页</el-button> <el-button v-if="activeTab === 'settings'" @click="activeTab = 'dashboard'" type="primary" plain>返回监控首页</el-button>
</div> </div>
<div class="header-right"> <div class="header-right">
@@ -24,8 +24,17 @@
<el-icon class="rotating-icon"><Refresh /></el-icon> <el-icon class="rotating-icon"><Refresh /></el-icon>
<span class="refresh-text">{{ countdown }}秒后自动刷新</span> <span class="refresh-text">{{ countdown }}秒后自动刷新</span>
</div> </div>
<el-button v-if="activeTab === 'dashboard'" @click="loadDashboard" type="primary" plain>刷新</el-button> <span class="header-stat" v-if="activeTab === 'dashboard'">网站总数:<b>{{ stats.totalWebsites }}</b></span>
<el-button @click="showAdminLogin" type="warning" plain v-if="!isAdmin">登录后台</el-button> <el-dropdown @command="handleDropdownCommand" v-if="!isAdmin">
<el-button link type="text" style="color: rgba(255,255,255,0.8); font-size: 18px">
<el-icon><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="login"><el-icon><Lock /></el-icon> 登录后台</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown @command="handleAdminCommand" v-else> <el-dropdown @command="handleAdminCommand" v-else>
<el-button type="success" plain>后台管理 <el-icon><arrow-down /></el-icon></el-button> <el-button type="success" plain>后台管理 <el-icon><arrow-down /></el-icon></el-button>
<template #dropdown> <template #dropdown>
@@ -42,19 +51,6 @@
<el-main> <el-main>
<!-- 监控仪表板 --> <!-- 监控仪表板 -->
<div v-if="activeTab === 'dashboard'"> <div v-if="activeTab === 'dashboard'">
<div class="dashboard-header">
<h2>网站监控仪表板</h2>
<div class="dashboard-stats">
<el-statistic title="网站总数" :value="stats.totalWebsites" />
<el-statistic title="在线" :value="stats.onlineCount">
<template #suffix><el-icon><circle-check /></el-icon></template>
</el-statistic>
<el-statistic title="离线" :value="stats.offlineCount">
<template #suffix><el-icon><circle-close /></el-icon></template>
</el-statistic>
<el-statistic title="未检测" :value="stats.unknownCount" />
</div>
</div>
<div v-for="user in dashboardData" :key="user.id" class="user-section"> <div v-for="user in dashboardData" :key="user.id" class="user-section">
<div class="user-header"> <div class="user-header">
@@ -65,6 +61,13 @@
</div> </div>
<el-table :data="user.websites" style="width: 100%; margin-bottom: 20px"> <el-table :data="user.websites" style="width: 100%; margin-bottom: 20px">
<el-table-column label="启用状态" width="90">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'" size="small">
{{ scope.row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="name" label="网站名称" width="150"></el-table-column> <el-table-column prop="name" label="网站名称" width="150"></el-table-column>
<el-table-column prop="url" label="URL" width="250"> <el-table-column prop="url" label="URL" width="250">
<template #default="scope"> <template #default="scope">
@@ -98,6 +101,10 @@
</div> </div>
<el-empty v-if="dashboardData.length === 0" description="暂无网站数据"></el-empty> <el-empty v-if="dashboardData.length === 0" description="暂无网站数据"></el-empty>
<div class="dashboard-footer">
<p>免责声明:本系统仅用于Cookie状态监控和自动化管理,不对任何网站数据承担责任。请确保您有权访问和管理相关网站账号。</p>
</div>
</div> </div>
<!-- 网站管理 --> <!-- 网站管理 -->
-97
View File
@@ -1,97 +0,0 @@
{
"users": [
{
"name": "小余",
"cookie_cloud": {
"uuid": "hu6n2vcUmzpu7mqUN2rVCg",
"password": "jtDM5dV9AyqXkZdQVeA9f6",
"api_url": "https://movie-pilot.org/cookiecloud"
},
"notification": {
"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191"
},
"browser": {
"type": "edge",
"headless": false
},
"websites": [
{
"name": "柴油联名卡",
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"login_check_selector": "",
"success_text": "彭峰"
}
]
},
{
"name": "彭峰(个人)",
"cookie_cloud": {
"uuid": "qyZpTrgiP5mVwiRZwfBhjz",
"password": "iGn1B4FnWftp3oj4Ko3jxA",
"api_url": "http://192.168.1.100:3000/cookiecloud"
},
"notification": {
"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191"
},
"browser": {
"type": "edge",
"headless": false
},
"websites": [
{
"name": "柴油联名卡",
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"login_check_selector": "",
"success_text": "彭峰"
},
{
"name": "网站B",
"url": "https://192.168.1.200:8006/",
"login_check_selector": "",
"success_text": "root@pam"
}
]
},
{
"name": "彭峰(公司)",
"cookie_cloud": {
"uuid": "aCgAWN5DsYhP88FnTmrr3H",
"password": "bK8dfh1a6ZZ79jWeeMedjN",
"api_url": "https://movie-pilot.org/cookiecloud"
},
"notification": {
"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191"
},
"browser": {
"type": "edge",
"headless": false
},
"websites": [
{
"name": "柴油联名卡",
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"login_check_selector": "",
"success_text": "彭峰"
},
{
"name": "加油卡交易",
"url": "https://cardweb.salecard.sinopec.com/card/index.jsp",
"login_check_selector": "",
"success_text": "彭峰"
},
{
"name": "数字销售门户",
"url": "https://ewcc.sinopec.com/?authConfigs=37d5aed075525d4fa0fe635231cba447",
"login_check_selector": "",
"success_text": "彭峰"
},
{
"name": "飞牛存储",
"url": "http://10.190.20.156:5666/",
"login_check_selector": "",
"success_text": "彭峰"
}
]
}
]
}
-192
View File
@@ -1,192 +0,0 @@
{
"小余": {
"lmkbi.95155.com": {
"cookies": [
{
"name": "_qimei_i_1",
"value": "5bd854d4c10f0489c79eff300ad57ae0f1bba6f017520a84e6862c582493206c616336c03980ebdd829cd4f1",
"domain": ".95155.com"
},
{
"name": "userType",
"value": "2",
"domain": "lmkbi.95155.com"
},
{
"name": "_qimei_fingerprint",
"value": "46d124002cf7d99787d08337dcd33c84",
"domain": ".95155.com"
},
{
"name": "LMKBIUSS",
"value": "eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOiIxMzU0ODU2OTY2NiIsInRpbWVzdGFtcCI6MTc3Mjg4NzYxMjgwOCwidG9rZW4iOiI3YWM0YjZkMS1lYzc2LTQ3NDgtOTM4MS1kZDUwZGI4NjM1NTEifQ==",
"domain": "lmkbi.95155.com"
},
{
"name": "edentoken",
"value": "13548569666",
"domain": "lmkbi.95155.com"
},
{
"name": "_qimei_uuid42",
"value": "1991d0e1933100350aa906e215996b94a17042ee55",
"domain": ".95155.com"
},
{
"name": "user",
"value": "13548569666",
"domain": "lmkbi.95155.com"
},
{
"name": "_qimei_h38",
"value": "fdedb6910aa906e215996b9402000001e1991d",
"domain": ".95155.com"
},
{
"name": "_qimei_i_3",
"value": "5be076d19c08548d939eff62098270e7f3e7a4f3145c0a8ab7dd2b0d24c5246b336337943c89e2aa8cb7",
"domain": ".95155.com"
}
],
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"website_name": "柴油联名卡"
}
},
"彭峰(个人)": {
"192.168.1.200:8006": {
"cookies": [
{
"name": "PVELangCookie",
"value": "zh_CN",
"domain": "192.168.1.200"
},
{
"name": "PVEAuthCookie",
"value": "PVE%3Aroot@pam%3A69ACCBCF%3A%3AwZbqJgSmRXadHue3IxPXURT4YqLAH5XluGp6nedMrZDn01QLvg4yjk1P1Nf08FXzZoH75gxA1USwhZXRbgWn9azugDgWhmwTF5eL2y4xWmYBhYP5Gk+J64hb2OgIr4WoJvxXcUceAFYurDorboz7ILSgJ+Rbwua+rPnmUV2qzYBjSw8r1AyOYTjbdjVjoTM4Erls1JMz1zTCOJFLcEQNgsR0BnXa5VmIb5rp8inRUkA8hRkuDtMp5+HnaF2R+KOEf9CmqjO5nf/48mlGYtg3Bj0wktIO/oX66fRJ4xljIoByLYJhgz03XrarIXxh3o+Foh2yFvhbLpRiwO2QjHQqeA%3D%3D",
"domain": "192.168.1.200"
}
],
"url": "https://192.168.1.200:8006/",
"website_name": "网站B"
},
"lmkbi.95155.com": {
"cookies": [
{
"name": "_qimei_uuid42",
"value": "1a302130f16100ca18af370a47cb8afce807c4a076",
"domain": ".95155.com"
},
{
"name": "_qimei_fingerprint",
"value": "50aa79f919511142818459f44ac38dee",
"domain": ".95155.com"
},
{
"name": "edentoken",
"value": "13548569666",
"domain": "lmkbi.95155.com"
},
{
"name": "LMKBIUSS",
"value": "eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOiIxMzU0ODU2OTY2NiIsInRpbWVzdGFtcCI6MTc3Mjg4NzYxMjgwOCwidG9rZW4iOiI3YWM0YjZkMS1lYzc2LTQ3NDgtOTM4MS1kZDUwZGI4NjM1NTEifQ==",
"domain": "lmkbi.95155.com"
},
{
"name": "userType",
"value": "2",
"domain": "lmkbi.95155.com"
},
{
"name": "_qimei_i_1",
"value": "23ed70d49d0f50dcc497f83153d525e3f0eef5f51508518ae5d97b582493206c6163649c39d8e1dcd4b1e482",
"domain": ".95155.com"
},
{
"name": "_qimei_i_3",
"value": "64c844d0c35e05dc9796fc355d8470e5a3eba5f0410e00d3e78c2d0e2f95293d306031943c89e29eb6a6",
"domain": ".95155.com"
},
{
"name": "user",
"value": "13548569666",
"domain": "lmkbi.95155.com"
},
{
"name": "_qimei_h38",
"value": "f8e0750618af370a47cb8afc0200000c91a302",
"domain": ".95155.com"
}
],
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"website_name": "柴油联名卡"
}
},
"彭峰(公司)": {
"lmkbi.95155.com": {
"cookies": [
{
"name": "_qimei_h38",
"value": "fdedb6910aa906e215996b9402000001e1991d",
"domain": ".95155.com"
},
{
"name": "userType",
"value": "2",
"domain": "lmkbi.95155.com"
},
{
"name": "LMKBIUSS",
"value": "eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOiIxMzU0ODU2OTY2NiIsInRpbWVzdGFtcCI6MTc3Mjg4NzYxMjgwOCwidG9rZW4iOiI3YWM0YjZkMS1lYzc2LTQ3NDgtOTM4MS1kZDUwZGI4NjM1NTEifQ==",
"domain": "lmkbi.95155.com"
},
{
"name": "edentoken",
"value": "13548569666",
"domain": "lmkbi.95155.com"
},
{
"name": "_qimei_i_3",
"value": "5be076d19c08548d939eff62098270e7f3e7a4f3145c0a8ab7dd2b0d24c5246b336337943c89e2aa8cb7",
"domain": ".95155.com"
},
{
"name": "_qimei_i_1",
"value": "5bd854d4c10f0489c79eff300ad57ae0f1bba6f017520a84e6862c582493206c616336c03980ebdd829cd4f1",
"domain": ".95155.com"
},
{
"name": "user",
"value": "13548569666",
"domain": "lmkbi.95155.com"
},
{
"name": "_qimei_fingerprint",
"value": "46d124002cf7d99787d08337dcd33c84",
"domain": ".95155.com"
},
{
"name": "_qimei_uuid42",
"value": "1991d0e1933100350aa906e215996b94a17042ee55",
"domain": ".95155.com"
}
],
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"website_name": "柴油联名卡"
},
"cardweb.salecard.sinopec.com": {
"cookies": [],
"url": "https://cardweb.salecard.sinopec.com/card/index.jsp",
"website_name": "加油卡交易"
},
"ewcc.sinopec.com": {
"cookies": [],
"url": "https://ewcc.sinopec.com/?authConfigs=37d5aed075525d4fa0fe635231cba447",
"website_name": "数字销售门户"
},
"10.190.20.156:5666": {
"cookies": [],
"url": "http://10.190.20.156:5666/",
"website_name": "飞牛存储"
}
}
}
-7
View File
@@ -1,7 +0,0 @@
"""Cookie监控管理系统 - 检测客户端"""
import time
import requests
from datetime import datetime
from typing import Dict, List
from DrissionPage import ChromiumPage, ChromiumOptions
from utils.config import BACKEND_URL, BROWSER_TYPE, HEADLESS_MODE
+123 -14
View File
@@ -5,11 +5,20 @@ Cookie监控管理系统 - 检测客户端
然后分用户、分网站登录。 然后分用户、分网站登录。
""" """
import time import time
import logging
import requests import requests
from datetime import datetime from datetime import datetime
from typing import Dict, List, Optional from typing import Dict, List, Optional
from DrissionPage import ChromiumPage, ChromiumOptions from DrissionPage import ChromiumPage, ChromiumOptions
from config.config import BACKEND_URL, BROWSER_TYPE, HEADLESS_MODE, PAGE_LOAD_TIMEOUT from config.config import BACKEND_URL, BROWSER_TYPE, HEADLESS_MODE, PAGE_LOAD_TIMEOUT
from cookie_cloud_client import download_cookies, get_cookies_for_domain
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(name)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger('client')
class DetectionClient: class DetectionClient:
@@ -239,15 +248,40 @@ class DetectionClient:
# 获取网站的cookie # 获取网站的cookie
cookies = self.get_website_cookies(website_id) cookies = self.get_website_cookies(website_id)
if not cookies: if not cookies:
print(f" 警告: 未找到网站 {website_name} 的cookie") print(f" 警告: 未找到网站 {website_name} 的cookie,尝试从Cookie Cloud获取...")
self.save_detection_result( login_success = self._try_cookie_cloud_and_retry(user, website, url, check_selector, success_text)
website_id=website_id, response_time = time.time() - start_time
status=0,
response_time=time.time() - start_time, if login_success:
http_status=0, print(f" [OK] 登录验证成功")
message="未找到cookie" current_cookies = self.get_current_cookies()
) if current_cookies:
return False self.update_website_cookies(website_id, current_cookies)
print(f" 已更新cookie ({len(current_cookies)} 个)")
self.save_detection_result(
website_id=website_id,
status=1,
response_time=response_time,
http_status=200,
message="登录验证成功(Cookie Cloud首次导入)"
)
return True
else:
print(f" [FAIL] Cookie Cloud重试失败")
self.failed_websites.append({
'user': user,
'website': website,
'reason': '未找到cookie且Cookie Cloud重试失败'
})
self.save_detection_result(
website_id=website_id,
status=0,
response_time=response_time,
http_status=0,
message="未找到cookie且Cookie Cloud重试失败"
)
return False
# 导入cookie到浏览器 # 导入cookie到浏览器
from urllib.parse import urlparse from urllib.parse import urlparse
@@ -264,8 +298,14 @@ class DetectionClient:
login_success = self._check_login_with_retry(url, check_selector, success_text) login_success = self._check_login_with_retry(url, check_selector, success_text)
response_time = time.time() - start_time response_time = time.time() - start_time
# 如果仍然失败,尝试从Cookie Cloud下载新cookie并重试
if not login_success:
print(f" 常规检测全部失败,尝试从Cookie Cloud更新cookie...")
login_success = self._try_cookie_cloud_and_retry(user, website, url, check_selector, success_text)
response_time = time.time() - start_time
if login_success: if login_success:
print(f" 登录验证成功") print(f" [OK] 登录验证成功")
# 获取最新cookie并保存到后台 # 获取最新cookie并保存到后台
current_cookies = self.get_current_cookies() current_cookies = self.get_current_cookies()
@@ -283,13 +323,13 @@ class DetectionClient:
) )
return True return True
else: else:
print(f" 登录验证失败(连续6次检测未登录") print(f" [FAIL] 登录验证失败(含Cookie Cloud重试")
# 记录失败信息 # 记录失败信息
self.failed_websites.append({ self.failed_websites.append({
'user': user, 'user': user,
'website': website, 'website': website,
'reason': '登录验证失败(连续6次检测未登录' 'reason': '登录验证失败(含Cookie Cloud重试'
}) })
# 保存检测结果 # 保存检测结果
@@ -298,13 +338,13 @@ class DetectionClient:
status=0, status=0,
response_time=response_time, response_time=response_time,
http_status=200, http_status=200,
message="登录验证失败(连续6次检测未登录" message="登录验证失败(含Cookie Cloud重试"
) )
return False return False
except Exception as e: except Exception as e:
response_time = time.time() - start_time response_time = time.time() - start_time
print(f" 检测异常: {e}") print(f" [FAIL] 检测异常: {e}")
# 记录失败信息 # 记录失败信息
self.failed_websites.append({ self.failed_websites.append({
@@ -323,6 +363,75 @@ class DetectionClient:
) )
return False return False
def _try_cookie_cloud_and_retry(self, user: Dict, website: Dict, url: str,
check_selector: str, success_text: str) -> bool:
"""
尝试从Cookie Cloud下载新cookie并重新检测登录
Returns:
是否登录成功
"""
from urllib.parse import urlparse
parsed = urlparse(url)
domain = parsed.netloc
cc_uuid = user.get('cookie_cloud_uuid')
cc_password = user.get('cookie_cloud_password')
cc_api_url = user.get('cookie_cloud_api_url', 'https://movie-pilot.org/cookiecloud')
if not cc_uuid or not cc_password:
logger.info(f" 用户未配置Cookie Cloud,跳过 (UUID: {cc_uuid})")
return False
logger.info(f" ==================== Cookie Cloud 下载开始 ====================")
logger.info(f" 目标域名: {domain}")
logger.info(f" API地址: {cc_api_url}")
logger.info(f" UUID: {cc_uuid}")
logger.info(f" Password: {'*' * len(cc_password)}")
try:
cookies_data = download_cookies(cc_api_url, cc_uuid, cc_password)
logger.info(f" 下载完成,总域名数: {len(cookies_data) if isinstance(cookies_data, dict) else 0}")
domain_cookies = get_cookies_for_domain(cookies_data, domain)
if not domain_cookies:
logger.info(f" Cookie Cloud中未找到 {domain} 的cookie")
logger.info(f" ==================== Cookie Cloud 下载结束 ====================")
return False
logger.info(f" 从Cookie Cloud获取到 {len(domain_cookies)} 个cookie")
logger.info(f" 导入并重试...")
try:
self.browser.clear_cache()
logger.info(f" 已清除浏览器缓存")
except Exception as e:
logger.warning(f" 清除缓存失败: {e}")
imported = self.import_cookies_to_browser(domain_cookies, domain)
logger.info(f" 导入cookie: {imported}/{len(domain_cookies)} 个成功")
logger.info(f" 访问网站: {url}")
self.browser.get(url)
self.browser.wait.doc_loaded(timeout=PAGE_LOAD_TIMEOUT)
time.sleep(3)
if self._is_logged_in(check_selector, success_text):
logger.info(f" Cookie Cloud cookie登录成功")
logger.info(f" ==================== Cookie Cloud 下载结束 ====================")
return True
logger.info(f" Cookie Cloud cookie登录失败")
logger.info(f" ==================== Cookie Cloud 下载结束 ====================")
return False
except Exception as e:
logger.error(f" Cookie Cloud异常: {e}")
logger.info(f" ==================== Cookie Cloud 下载结束 ====================")
return False
def _check_login_with_retry(self, url: str, check_selector: str, success_text: str, def _check_login_with_retry(self, url: str, check_selector: str, success_text: str,
retry_count: int = 6, retry_interval: int = 30) -> bool: retry_count: int = 6, retry_interval: int = 30) -> bool:
""" """
+6
View File
@@ -0,0 +1,6 @@
{
"backend_url": "http://localhost:5000",
"browser_type": "edge",
"headless_mode": false,
"page_load_timeout": 30
}
+31 -10
View File
@@ -1,15 +1,36 @@
""" """
配置文件 配置文件 - 从 config.json 读取配置,支持环境变量覆盖
""" """
# 后台服务地址 import json
BACKEND_URL = "http://localhost:5000" import os
# 浏览器配置 _config = {}
BROWSER_TYPE = "edge" # edge 或 chrome
HEADLESS_MODE = False # 是否无头模式
# 检测间隔(秒) def _load_config():
CHECK_INTERVAL = 30 global _config
config_path = os.path.join(os.path.dirname(__file__), 'config.json')
defaults = {
'backend_url': 'http://localhost:5000',
'browser_type': 'edge',
'headless_mode': False,
'page_load_timeout': 30
}
if os.path.exists(config_path):
with open(config_path, 'r', encoding='utf-8') as f:
_config = {**defaults, **json.load(f)}
else:
_config = defaults
# 页面加载超时时间(秒) def get(key, default=None):
PAGE_LOAD_TIMEOUT = 30 if not _config:
_load_config()
return _config.get(key.upper(), _config.get(key, default))
# 兼容旧接口
BACKEND_URL = os.environ.get('BACKEND_URL') or get('backend_url', 'http://localhost:5000')
BROWSER_TYPE = os.environ.get('BROWSER_TYPE') or get('browser_type', 'edge')
HEADLESS_MODE = os.environ.get('HEADLESS_MODE') or get('headless_mode', False)
if isinstance(HEADLESS_MODE, str):
HEADLESS_MODE = HEADLESS_MODE.lower() in ('true', '1', 'yes')
CHECK_INTERVAL = int(os.environ.get('CHECK_INTERVAL') or get('check_interval', 30))
PAGE_LOAD_TIMEOUT = int(os.environ.get('PAGE_LOAD_TIMEOUT') or get('page_load_timeout', 30))
+126
View File
@@ -0,0 +1,126 @@
"""
Cookie Cloud 客户端(独立前端使用)
"""
import json
import hashlib
import logging
import requests
from base64 import b64decode
logger = logging.getLogger('cookie_cloud')
try:
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import unpad
except ImportError:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def cookie_decrypt(uuid: str, encrypted_b64: str, password: str) -> dict:
"""解密Cookie Cloud数据"""
the_key = hashlib.md5(f"{uuid}-{password}".encode()).hexdigest()[:16].encode()
encrypted_data = b64decode(encrypted_b64)
logger.info("尝试AES-ECB解密...")
try:
cipher = AES.new(the_key, AES.MODE_ECB)
decrypted = cipher.decrypt(encrypted_data)
result = unpad(decrypted, AES.block_size)
data = json.loads(result.decode('utf-8'))
logger.info("AES-ECB解密成功")
return data
except Exception as e:
logger.info(f"AES-ECB解密失败: {e},尝试Salted__格式...")
pass
if encrypted_data[:8] == b'Salted__':
logger.info("检测到Salted__格式,尝试CBC解密...")
salt = encrypted_data[8:16]
ciphertext = encrypted_data[16:]
logger.info(f"Salt: {salt.hex()}")
d = [b'']
while len(b''.join(d)) < 48:
d.append(hashlib.md5(d[-1] + the_key + salt).digest())
key_iv = b''.join(d)
cipher = AES.new(key_iv[:32], AES.MODE_CBC, key_iv[32:48])
decrypted = unpad(cipher.decrypt(ciphertext), AES.block_size)
data = json.loads(decrypted.decode('utf-8'))
logger.info("CBC解密成功")
return data
logger.error("所有解密方式均失败")
raise ValueError('无法解密Cookie数据')
def download_cookies(api_url: str, uuid: str, password: str) -> dict:
"""从Cookie Cloud服务器下载并解密Cookie"""
api_url = api_url.rstrip('/')
url = f"{api_url}/get/{uuid}"
logger.info(f"[1/3] 请求Cookie Cloud: {url}")
logger.info(f"[1/3] UUID: {uuid}")
response = requests.get(url, params={'password': password}, timeout=10)
response.raise_for_status()
result = response.json()
logger.info(f"[1/3] HTTP状态码: {response.status_code}")
logger.info(f"[1/3] 返回数据keys: {list(result.keys())}")
logger.info(f"[1/3] 返回数据大小: {len(json.dumps(result))} bytes")
if isinstance(result, dict) and result.get('encrypted'):
logger.info("[2/3] 检测到加密数据,开始解密...")
result = cookie_decrypt(uuid, result['encrypted'], password)
logger.info(f"[2/3] 解密完成,数据类型: {type(result)}")
if isinstance(result, dict):
logger.info(f"[2/3] Cookie数据包含域名: {list(result.keys())}")
if isinstance(result, dict) and 'cookie_data' in result:
cookies_data = result.get('cookie_data', {})
logger.info(f"[3/3] 新版CookieCloud格式,提取cookie_data")
logger.info(f"[3/3] cookie_data包含域名数: {len(cookies_data)}")
return cookies_data
logger.info(f"[3/3] 返回数据已处理完成")
return result
def get_cookies_for_domain(cookies_data: dict, domain: str) -> list:
"""提取指定域名对应的Cookie列表"""
result = []
matched_hosts = []
domain_parts = domain.lower().split('.')
for host, cookie_list in cookies_data.items():
host_clean = host.lower().lstrip('.')
domain_clean = domain.lower()
is_match = False
if host_clean == domain_clean:
is_match = True
elif host_clean.endswith('.' + domain_clean):
is_match = True
elif domain_clean.endswith('.' + host_clean):
is_match = True
if is_match:
matched_hosts.append(host)
if isinstance(cookie_list, list):
for cookie in cookie_list:
if cookie.get('name') and cookie.get('value'):
result.append({
'name': cookie['name'],
'value': cookie['value'],
'domain': cookie.get('domain', domain),
'path': cookie.get('path', '/')
})
logger.info(f"域名匹配结果: 目标={domain}, 匹配hosts={matched_hosts}")
logger.info(f"提取Cookie数量: {len(result)}")
if result:
cookie_names = [c['name'] for c in result[:5]]
logger.info(f"前5个Cookie名称: {cookie_names}")
return result
-55
View File
@@ -1,55 +0,0 @@
"""
更新用户Cookie Cloud参数
"""
import json
import sqlite3
import os
DB_PATH = os.path.join(os.path.dirname(__file__), 'backend', 'instance', 'cookie_monitor.db')
def update_user_cookie_cloud():
"""更新用户Cookie Cloud参数"""
with open('config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 添加新列(如果不存在)
columns_to_add = [
('cookie_cloud_uuid', 'VARCHAR(255)'),
('cookie_cloud_password', 'VARCHAR(255)'),
('cookie_cloud_api_url', 'VARCHAR(255)')
]
for col_name, col_type in columns_to_add:
try:
cursor.execute(f"ALTER TABLE users ADD COLUMN {col_name} {col_type}")
print(f"添加列: {col_name}")
except sqlite3.OperationalError as e:
if 'duplicate column name' in str(e).lower():
print(f"列已存在: {col_name}")
else:
raise
for user_config in config['users']:
user_name = user_config['name']
cc = user_config.get('cookie_cloud', {})
cursor.execute(
"UPDATE users SET cookie_cloud_uuid = ?, cookie_cloud_password = ?, cookie_cloud_api_url = ? WHERE name = ?",
(
cc.get('uuid', ''),
cc.get('password', ''),
cc.get('api_url', ''),
user_name
)
)
print(f"更新用户 {user_name}: UUID={cc.get('uuid', '')}, API={cc.get('api_url', '')}")
conn.commit()
conn.close()
print("\n更新完成!")
if __name__ == '__main__':
update_user_cookie_cloud()