项目整理:添加Cookie Cloud下载功能、优化前端界面、配置改为config.json、打包脚本、清理无用文件
This commit is contained in:
@@ -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
@@ -5,11 +5,20 @@ Cookie监控管理系统 - 检测客户端
|
||||
然后分用户、分网站登录。
|
||||
"""
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from DrissionPage import ChromiumPage, ChromiumOptions
|
||||
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:
|
||||
@@ -239,15 +248,40 @@ class DetectionClient:
|
||||
# 获取网站的cookie
|
||||
cookies = self.get_website_cookies(website_id)
|
||||
if not cookies:
|
||||
print(f" 警告: 未找到网站 {website_name} 的cookie")
|
||||
self.save_detection_result(
|
||||
website_id=website_id,
|
||||
status=0,
|
||||
response_time=time.time() - start_time,
|
||||
http_status=0,
|
||||
message="未找到cookie"
|
||||
)
|
||||
return False
|
||||
print(f" 警告: 未找到网站 {website_name} 的cookie,尝试从Cookie Cloud获取...")
|
||||
login_success = self._try_cookie_cloud_and_retry(user, website, url, check_selector, success_text)
|
||||
response_time = time.time() - start_time
|
||||
|
||||
if login_success:
|
||||
print(f" [OK] 登录验证成功")
|
||||
current_cookies = self.get_current_cookies()
|
||||
if current_cookies:
|
||||
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到浏览器
|
||||
from urllib.parse import urlparse
|
||||
@@ -264,8 +298,14 @@ class DetectionClient:
|
||||
login_success = self._check_login_with_retry(url, check_selector, success_text)
|
||||
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:
|
||||
print(f" ✓ 登录验证成功")
|
||||
print(f" [OK] 登录验证成功")
|
||||
|
||||
# 获取最新cookie并保存到后台
|
||||
current_cookies = self.get_current_cookies()
|
||||
@@ -283,13 +323,13 @@ class DetectionClient:
|
||||
)
|
||||
return True
|
||||
else:
|
||||
print(f" ✗ 登录验证失败(连续6次检测未登录)")
|
||||
print(f" [FAIL] 登录验证失败(含Cookie Cloud重试)")
|
||||
|
||||
# 记录失败信息
|
||||
self.failed_websites.append({
|
||||
'user': user,
|
||||
'website': website,
|
||||
'reason': '登录验证失败(连续6次检测未登录)'
|
||||
'reason': '登录验证失败(含Cookie Cloud重试)'
|
||||
})
|
||||
|
||||
# 保存检测结果
|
||||
@@ -298,13 +338,13 @@ class DetectionClient:
|
||||
status=0,
|
||||
response_time=response_time,
|
||||
http_status=200,
|
||||
message="登录验证失败(连续6次检测未登录)"
|
||||
message="登录验证失败(含Cookie Cloud重试)"
|
||||
)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
response_time = time.time() - start_time
|
||||
print(f" ✗ 检测异常: {e}")
|
||||
print(f" [FAIL] 检测异常: {e}")
|
||||
|
||||
# 记录失败信息
|
||||
self.failed_websites.append({
|
||||
@@ -323,6 +363,75 @@ class DetectionClient:
|
||||
)
|
||||
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,
|
||||
retry_count: int = 6, retry_interval: int = 30) -> bool:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"backend_url": "http://localhost:5000",
|
||||
"browser_type": "edge",
|
||||
"headless_mode": false,
|
||||
"page_load_timeout": 30
|
||||
}
|
||||
+31
-10
@@ -1,15 +1,36 @@
|
||||
"""
|
||||
配置文件
|
||||
配置文件 - 从 config.json 读取配置,支持环境变量覆盖
|
||||
"""
|
||||
# 后台服务地址
|
||||
BACKEND_URL = "http://localhost:5000"
|
||||
import json
|
||||
import os
|
||||
|
||||
# 浏览器配置
|
||||
BROWSER_TYPE = "edge" # edge 或 chrome
|
||||
HEADLESS_MODE = False # 是否无头模式
|
||||
_config = {}
|
||||
|
||||
# 检测间隔(秒)
|
||||
CHECK_INTERVAL = 30
|
||||
def _load_config():
|
||||
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
|
||||
|
||||
# 页面加载超时时间(秒)
|
||||
PAGE_LOAD_TIMEOUT = 30
|
||||
def get(key, default=None):
|
||||
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))
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user