update
This commit is contained in:
+121
-10
@@ -129,6 +129,99 @@ class BrowserLogin:
|
||||
logger.error(f"检查登录状态失败: {e}")
|
||||
return False
|
||||
|
||||
def import_all_cookies(self, cookies: List[Dict]) -> bool:
|
||||
"""
|
||||
一次性导入所有 cookies 到浏览器
|
||||
|
||||
Args:
|
||||
cookies: cookie 列表
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
if not self.browser or not self.tab:
|
||||
self._create_browser()
|
||||
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
logger.info(f"开始导入 {len(cookies)} 个 cookie")
|
||||
for cookie in cookies:
|
||||
try:
|
||||
cookie_dict = {
|
||||
'name': cookie.get('name', ''),
|
||||
'value': cookie.get('value', ''),
|
||||
'domain': cookie.get('domain', ''),
|
||||
'path': cookie.get('path', '/'),
|
||||
}
|
||||
if cookie.get('secure'):
|
||||
cookie_dict['secure'] = True
|
||||
if cookie.get('httpOnly'):
|
||||
cookie_dict['httpOnly'] = True
|
||||
if cookie.get('sameSite'):
|
||||
cookie_dict['sameSite'] = cookie.get('sameSite')
|
||||
|
||||
self.tab.set.cookies(cookie_dict)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
fail_count += 1
|
||||
|
||||
logger.info(f"Cookie 导入完成: 成功 {success_count}, 失败 {fail_count}")
|
||||
return fail_count == 0
|
||||
|
||||
def verify_login(self, url: str, check_selector: str = "", success_text: str = "") -> bool:
|
||||
"""
|
||||
验证登录状态(cookies已预先导入)
|
||||
|
||||
Args:
|
||||
url: 网站地址
|
||||
check_selector: 登录检测选择器(可选)
|
||||
success_text: 登录成功时显示的文本(可选)
|
||||
|
||||
Returns:
|
||||
是否已登录
|
||||
"""
|
||||
if not self.browser or not self.tab:
|
||||
self._create_browser()
|
||||
|
||||
try:
|
||||
self.tab.get(url)
|
||||
time.sleep(5)
|
||||
|
||||
# 如果没有指定选择器,只检查页面是否成功加载
|
||||
if not check_selector:
|
||||
logger.info(" 未指定登录检测选择器,跳过登录验证")
|
||||
return True
|
||||
|
||||
# 尝试查找元素
|
||||
try:
|
||||
element = self.tab.ele(check_selector, timeout=10)
|
||||
|
||||
if element:
|
||||
element_text = element.text or ""
|
||||
logger.info(f" 找到元素,文本内容: {element_text[:50]}...")
|
||||
|
||||
if not success_text:
|
||||
logger.info(" 未指定成功文本,找到元素即认为登录成功")
|
||||
return True
|
||||
|
||||
if success_text in element_text:
|
||||
logger.info(f" 检测到成功文本: {success_text}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f" 未检测到成功文本 '{success_text}',元素文本: {element_text[:100]}")
|
||||
return False
|
||||
else:
|
||||
logger.warning(f" 未找到元素: {check_selector}")
|
||||
return False
|
||||
except Exception as ele_error:
|
||||
logger.warning(f" 查找元素失败: {ele_error}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"验证登录状态失败: {e}")
|
||||
return False
|
||||
|
||||
def login_with_cookies(
|
||||
self,
|
||||
url: str,
|
||||
@@ -152,20 +245,38 @@ class BrowserLogin:
|
||||
self._create_browser()
|
||||
|
||||
try:
|
||||
# 访问网站
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(url)
|
||||
base_url = f"{parsed.scheme}://{parsed.netloc}"
|
||||
|
||||
self.tab.get(base_url)
|
||||
time.sleep(1)
|
||||
|
||||
logger.info(f"开始设置 {len(cookies)} 个 cookie")
|
||||
for cookie in cookies:
|
||||
try:
|
||||
cookie_dict = {
|
||||
'name': cookie.get('name', ''),
|
||||
'value': cookie.get('value', ''),
|
||||
'domain': cookie.get('domain', parsed.netloc),
|
||||
'path': cookie.get('path', '/'),
|
||||
}
|
||||
if cookie.get('secure'):
|
||||
cookie_dict['secure'] = True
|
||||
if cookie.get('httpOnly'):
|
||||
cookie_dict['httpOnly'] = True
|
||||
if cookie.get('sameSite'):
|
||||
cookie_dict['sameSite'] = cookie.get('sameSite')
|
||||
|
||||
self.tab.set.cookies(cookie_dict)
|
||||
logger.info(f"设置 cookie: {cookie_dict['name']}")
|
||||
except Exception as e:
|
||||
logger.error(f"设置 cookie 失败 {cookie.get('name')}: {e}")
|
||||
|
||||
self.tab.get(url)
|
||||
|
||||
# 添加 cookies
|
||||
for cookie in cookies:
|
||||
self.tab.set.cookies(cookie)
|
||||
|
||||
# 刷新页面
|
||||
self.tab.refresh()
|
||||
|
||||
# 等待页面加载
|
||||
time.sleep(3)
|
||||
|
||||
# 检查登录状态
|
||||
element = self.tab.ele(check_selector, timeout=10)
|
||||
|
||||
if element and element.text and success_text in element.text:
|
||||
|
||||
+52
-1
@@ -1 +1,52 @@
|
||||
{"users": [{"name": "用户A", "cookie_cloud": {"uuid": "hu6n2vcUmzpu7mqUN2rVCg", "password": "jtDM5dV9AyqXkZdQVeA9f6", "api_url": "https://movie-pilot.org/cookiecloud"}, "notification": {"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191", "max_fail_count": 3}, "browser": {"type": "edge", "headless": false}, "websites": [{"name": "网站A", "url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll", "login_check_selector": "#user-info", "success_text": "彭峰"}]}]}
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"name": "用户A",
|
||||
"cookie_cloud": {
|
||||
"uuid": "hu6n2vcUmzpu7mqUN2rVCg",
|
||||
"password": "jtDM5dV9AyqXkZdQVeA9f6",
|
||||
"api_url": "https://movie-pilot.org/cookiecloud"
|
||||
},
|
||||
"notification": {
|
||||
"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191",
|
||||
"max_fail_count": 3
|
||||
},
|
||||
"browser": {
|
||||
"type": "edge",
|
||||
"headless": false
|
||||
},
|
||||
"websites": [
|
||||
{
|
||||
"name": "网站A",
|
||||
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
|
||||
"login_check_selector": "",
|
||||
"success_text": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "用户B",
|
||||
"cookie_cloud": {
|
||||
"uuid": "qyZpTrgiP5mVwiRZwfBhjz",
|
||||
"password": "iGn1B4FnWftp3oj4Ko3jxA",
|
||||
"api_url": "http://192.168.1.100:3000/cookiecloud"
|
||||
},
|
||||
"notification": {
|
||||
"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191",
|
||||
"max_fail_count": 3
|
||||
},
|
||||
"browser": {
|
||||
"type": "edge",
|
||||
"headless": false
|
||||
},
|
||||
"websites": [
|
||||
{
|
||||
"name": "网站A",
|
||||
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
|
||||
"login_check_selector": "",
|
||||
"success_text": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+111
-118
@@ -1,11 +1,11 @@
|
||||
"""Cookie Cloud 模块 - 从 Cookie Cloud 服务器获取并解密 cookie 数据"""
|
||||
import hashlib
|
||||
"""Cookie Cloud 模块 - 从 Cookie Cloud 服务器获取 cookie 数据"""
|
||||
import json
|
||||
import hashlib
|
||||
import base64
|
||||
from typing import Dict, List, Optional
|
||||
import requests
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from applogger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -27,77 +27,82 @@ class CookieCloud:
|
||||
self.uuid = uuid
|
||||
self.password = password
|
||||
|
||||
def decrypt(self, encrypted_data: str) -> Dict:
|
||||
"""
|
||||
解密 Cookie Cloud 数据
|
||||
def _get_crypt_key(self) -> bytes:
|
||||
"""生成加密密钥"""
|
||||
combined_string = f"{self.uuid}-{self.password}"
|
||||
return hashlib.md5(combined_string.encode('utf-8')).hexdigest()[:16].encode("utf-8")
|
||||
|
||||
Args:
|
||||
encrypted_data: 加密的数据字符串
|
||||
def _bytes_to_key(self, data: bytes, salt: bytes, output: int = 48) -> bytes:
|
||||
"""OpenSSL EVP_BytesToKey 密钥派生算法"""
|
||||
assert len(salt) == 8, len(salt)
|
||||
data += salt
|
||||
key = hashlib.md5(data).digest()
|
||||
final_key = key
|
||||
while len(final_key) < output:
|
||||
key = hashlib.md5(key + data).digest()
|
||||
final_key += key
|
||||
return final_key[:output]
|
||||
|
||||
Returns:
|
||||
解密后的数据字典,包含 cookie_data 和 local_storage_data
|
||||
"""
|
||||
# 生成解密密钥: md5(uuid+password) 取前 16 个字符
|
||||
key_str = f"{self.uuid}{self.password}"
|
||||
key = hashlib.md5(key_str.encode()).hexdigest()[:16].encode()
|
||||
print(f"生成的密钥: {key}")
|
||||
print(f"密钥长度: {len(key)}")
|
||||
|
||||
# 解码 Base64
|
||||
encrypted = base64.b64decode(encrypted_data)
|
||||
print(f"Base64 解码后长度: {len(encrypted)}")
|
||||
print(f"解码后前 20 字节: {encrypted[:20]}")
|
||||
|
||||
# 检查是否有盐值(OpenSSL 格式)
|
||||
if encrypted.startswith(b'Salted__'):
|
||||
print("检测到 OpenSSL 格式数据")
|
||||
# 对于 OpenSSL 格式,我们需要跳过盐值部分
|
||||
# 但根据用户要求,我们直接使用16字符密钥,不考虑盐值
|
||||
ciphertext = encrypted[16:] # 跳过 "Salted__" 和盐值
|
||||
print(f"密文长度: {len(ciphertext)}")
|
||||
else:
|
||||
print("未检测到 OpenSSL 格式,使用整个数据作为密文")
|
||||
ciphertext = encrypted
|
||||
|
||||
# 使用固定的 IV (16 个 0 字节)
|
||||
iv = b'\x00' * 16
|
||||
print(f"使用的 IV: {iv}")
|
||||
|
||||
# AES-128-CBC 解密(16字节密钥)
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
decrypted = cipher.decrypt(ciphertext)
|
||||
print(f"解密后长度: {len(decrypted)}")
|
||||
print(f"解密后前 100 字节: {decrypted[:100]}")
|
||||
|
||||
def _decrypt(self, encrypted: str, passphrase: bytes) -> bytes:
|
||||
"""解密数据"""
|
||||
encrypted_bytes = base64.b64decode(encrypted)
|
||||
assert encrypted_bytes.startswith(b"Salted__"), "Invalid encrypted data format"
|
||||
salt = encrypted_bytes[8:16]
|
||||
key_iv = self._bytes_to_key(passphrase, salt, 32 + 16)
|
||||
key = key_iv[:32]
|
||||
iv = key_iv[32:]
|
||||
aes = AES.new(key, AES.MODE_CBC, iv)
|
||||
decrypted_padded = aes.decrypt(encrypted_bytes[16:])
|
||||
padding_length = decrypted_padded[-1]
|
||||
if isinstance(padding_length, str):
|
||||
padding_length = ord(padding_length)
|
||||
return decrypted_padded[:-padding_length]
|
||||
|
||||
def _download_data(self) -> Tuple[Optional[Dict], str]:
|
||||
"""下载并解密所有数据"""
|
||||
try:
|
||||
# 移除 PKCS#7 填充
|
||||
decrypted = unpad(decrypted, AES.block_size)
|
||||
print(f"移除填充后长度: {len(decrypted)}")
|
||||
print(f"移除填充后前 100 字节: {decrypted[:100]}")
|
||||
url = f"{self.api_url}/get/{self.uuid}"
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'CookieCloud-Client/1.0'
|
||||
},
|
||||
method='GET'
|
||||
)
|
||||
|
||||
# 尝试解析 JSON
|
||||
result = json.loads(decrypted.decode('utf-8'))
|
||||
print("JSON 解析成功")
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"解密失败: {e}")
|
||||
# 尝试直接截取可能的有效数据
|
||||
response = urllib.request.urlopen(request, timeout=30)
|
||||
|
||||
if response.status != 200:
|
||||
return None, f"服务器返回错误状态码: {response.status}"
|
||||
|
||||
result = json.loads(response.read().decode('utf-8'))
|
||||
|
||||
if not result:
|
||||
return None, "服务器返回数据为空"
|
||||
|
||||
encrypted = result.get("encrypted")
|
||||
if not encrypted:
|
||||
return None, "未获取到cookie密文"
|
||||
|
||||
crypt_key = self._get_crypt_key()
|
||||
try:
|
||||
# 尝试找到 JSON 开始的位置
|
||||
for i in range(len(decrypted)):
|
||||
try:
|
||||
test_data = decrypted[i:]
|
||||
test_str = test_data.decode('utf-8', errors='ignore')
|
||||
if test_str.strip().startswith('{'):
|
||||
print(f"找到可能的 JSON 开始位置: {i}")
|
||||
result = json.loads(test_str)
|
||||
print("JSON 解析成功(直接截取)")
|
||||
return result
|
||||
except:
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
raise
|
||||
decrypted_data = self._decrypt(encrypted, crypt_key)
|
||||
result = json.loads(decrypted_data.decode("utf-8"))
|
||||
except Exception as e:
|
||||
return None, f"cookie解密失败: {str(e)}"
|
||||
|
||||
if not result:
|
||||
return None, "cookie解密为空"
|
||||
|
||||
return result, ""
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
return None, f"HTTP错误: {e.code} {e.reason}"
|
||||
except urllib.error.URLError as e:
|
||||
return None, f"网络连接失败: {e.reason}"
|
||||
except Exception as e:
|
||||
return None, f"下载失败: {str(e)}"
|
||||
|
||||
def get_cookies(self) -> Dict[str, List[Dict]]:
|
||||
"""
|
||||
@@ -107,49 +112,34 @@ class CookieCloud:
|
||||
按域名分组的 cookie 数据字典
|
||||
格式: {"domain.com": [{"name": "cookie1", "value": "val1", ...}, ...], ...}
|
||||
"""
|
||||
url = f"{self.api_url}/get/{self.uuid}"
|
||||
data, error = self._download_data()
|
||||
|
||||
try:
|
||||
response = requests.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
logger.info(f"Cookie Cloud 响应: {json.dumps(data, indent=2)}")
|
||||
|
||||
if not data or 'encrypted' not in data:
|
||||
logger.error("响应中没有 encrypted 字段")
|
||||
return {}
|
||||
|
||||
# 打印完整的加密数据长度和前200个字符
|
||||
logger.info(f"加密数据长度: {len(data['encrypted'])}")
|
||||
logger.info(f"加密数据前 200 个字符: {data['encrypted'][:200]}...")
|
||||
|
||||
try:
|
||||
decrypted_data = self.decrypt(data['encrypted'])
|
||||
logger.info(f"解密后的数据: {json.dumps(decrypted_data, indent=2)}")
|
||||
except Exception as e:
|
||||
logger.error(f"解密失败: {e}")
|
||||
return {}
|
||||
|
||||
# 解析 cookie_data
|
||||
cookie_dict = {}
|
||||
if 'cookie_data' in decrypted_data:
|
||||
for domain, cookies in decrypted_data['cookie_data'].items():
|
||||
cookie_dict[domain] = []
|
||||
for cookie in cookies:
|
||||
# 处理 sameSite 字段
|
||||
if cookie.get('sameSite') == 'unspecified':
|
||||
cookie['sameSite'] = 'Lax'
|
||||
cookie_dict[domain].append(cookie)
|
||||
|
||||
return cookie_dict
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"请求 Cookie Cloud 失败: {e}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(f"处理 Cookie Cloud 数据失败: {e}")
|
||||
if error:
|
||||
logger.error(error)
|
||||
return {}
|
||||
|
||||
# 处理数据结构
|
||||
cookie_data = {}
|
||||
|
||||
# 兼容直接按域名分组的格式
|
||||
if isinstance(data, dict) and not data.get('cookie_data'):
|
||||
cookie_data = data
|
||||
# 兼容包含 cookie_data 的格式
|
||||
elif isinstance(data, dict) and data.get('cookie_data'):
|
||||
cookie_data = data.get('cookie_data', {})
|
||||
|
||||
# 处理 sameSite 字段
|
||||
processed_cookies = {}
|
||||
for domain, cookies in cookie_data.items():
|
||||
if not cookies:
|
||||
continue
|
||||
processed_cookies[domain] = []
|
||||
for cookie in cookies:
|
||||
if cookie.get('sameSite') == 'unspecified':
|
||||
cookie['sameSite'] = 'Lax'
|
||||
processed_cookies[domain].append(cookie)
|
||||
|
||||
return processed_cookies
|
||||
|
||||
def get_cookies_for_domain(self, domain: str) -> List[Dict]:
|
||||
"""
|
||||
@@ -163,16 +153,16 @@ class CookieCloud:
|
||||
"""
|
||||
all_cookies = self.get_cookies()
|
||||
|
||||
# 查找匹配的域名
|
||||
matched_cookies = []
|
||||
for cookie_domain, cookies in all_cookies.items():
|
||||
if domain in cookie_domain or cookie_domain in domain:
|
||||
return cookies
|
||||
clean_domain = cookie_domain.lstrip('.')
|
||||
if domain in clean_domain or clean_domain in domain:
|
||||
matched_cookies.extend(cookies)
|
||||
|
||||
return []
|
||||
return matched_cookies
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试代码
|
||||
cc = CookieCloud(
|
||||
api_url="https://movie-pilot.org/cookiecloud",
|
||||
uuid="hu6n2vcUmzpu7mqUN2rVCg",
|
||||
@@ -180,4 +170,7 @@ if __name__ == "__main__":
|
||||
)
|
||||
|
||||
cookies = cc.get_cookies()
|
||||
logger.info(json.dumps(cookies, indent=2, ensure_ascii=False))
|
||||
logger.info(f"获取到 {len(cookies)} 个域名的 cookies")
|
||||
|
||||
lmkbi_cookies = cc.get_cookies_for_domain('lmkbi.95155.com')
|
||||
logger.info(f"获取到 {len(lmkbi_cookies)} 个 lmkbi.95155.com 的 cookies")
|
||||
|
||||
+106
-145
@@ -1,7 +1,5 @@
|
||||
"""Cookie 监控主程序"""
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from cookie_cloud import CookieCloud
|
||||
from notifier import IYUUNotifier, FailureTracker
|
||||
@@ -15,237 +13,200 @@ class CookieMonitor:
|
||||
"""Cookie 监控器"""
|
||||
|
||||
def __init__(self, config_file: str = "config.json"):
|
||||
"""
|
||||
初始化 Cookie 监控器
|
||||
|
||||
Args:
|
||||
config_file: 配置文件路径
|
||||
"""
|
||||
self.config_file = config_file
|
||||
self.config = self._load_config()
|
||||
self.cookie_manager = CookieManager("cookies.json")
|
||||
self.failure_tracker = FailureTracker("state.json")
|
||||
|
||||
def _load_config(self) -> dict:
|
||||
"""加载配置文件"""
|
||||
try:
|
||||
with open(self.config_file, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"加载配置文件失败: {e}")
|
||||
logger.error(f"加载配置文件失败: {e}")
|
||||
return {}
|
||||
|
||||
def _log(self, message: str):
|
||||
"""记录日志"""
|
||||
logger.info(message)
|
||||
|
||||
def _get_domain_from_url(self, url: str) -> str:
|
||||
"""从 URL 中提取域名"""
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(url)
|
||||
return parsed.netloc
|
||||
return urlparse(url).netloc
|
||||
|
||||
def process_user(self, user_config: dict):
|
||||
"""
|
||||
处理单个用户的所有网站
|
||||
|
||||
Args:
|
||||
user_config: 用户配置
|
||||
处理单个用户:一次性导入所有cookies,然后验证各网站登录状态
|
||||
"""
|
||||
user_name = user_config.get('name', '未知用户')
|
||||
self._log(f"开始处理用户: {user_name}")
|
||||
logger.info(f"{'='*20} 开始处理用户: {user_name} {'='*20}")
|
||||
|
||||
# 获取用户配置
|
||||
cookie_cloud_config = user_config.get('cookie_cloud', {})
|
||||
notification_config = user_config.get('notification', {})
|
||||
browser_config = user_config.get('browser', {})
|
||||
websites = user_config.get('websites', [])
|
||||
|
||||
# 初始化爱语飞飞通知
|
||||
if not websites:
|
||||
logger.info(f"用户 {user_name} 没有配置网站,跳过")
|
||||
return
|
||||
|
||||
iyuu_token = notification_config.get('iyuu_token', '')
|
||||
max_fail_count = notification_config.get('max_fail_count', 3)
|
||||
notifier = IYUUNotifier(iyuu_token) if iyuu_token else None
|
||||
|
||||
# 初始化 Cookie Cloud
|
||||
cookie_cloud = CookieCloud(
|
||||
api_url=cookie_cloud_config.get('api_url', ''),
|
||||
uuid=cookie_cloud_config.get('uuid', ''),
|
||||
password=cookie_cloud_config.get('password', '')
|
||||
)
|
||||
|
||||
# 初始化浏览器
|
||||
logger.info(f"步骤1: 从 Cookie Cloud 获取所有 cookies")
|
||||
all_cookies = cookie_cloud.get_cookies()
|
||||
total_domains = len(all_cookies)
|
||||
total_cookies = sum(len(c) for c in all_cookies.values())
|
||||
logger.info(f"获取到 {total_domains} 个域名,共 {total_cookies} 个 cookie")
|
||||
|
||||
browser = BrowserLogin(
|
||||
browser_type=browser_config.get('type', 'edge'),
|
||||
headless=browser_config.get('headless', False)
|
||||
)
|
||||
|
||||
results = []
|
||||
|
||||
try:
|
||||
# 处理每个网站
|
||||
logger.info(f"步骤2: 一次性导入所有 cookies 到浏览器")
|
||||
all_cookies_list = []
|
||||
for cookies in all_cookies.values():
|
||||
all_cookies_list.extend(cookies)
|
||||
|
||||
import_success = browser.import_all_cookies(all_cookies_list)
|
||||
if import_success:
|
||||
logger.info(f"成功导入 {len(all_cookies_list)} 个 cookie")
|
||||
else:
|
||||
logger.warning(f"部分 cookie 导入失败")
|
||||
|
||||
logger.info(f"步骤3: 依次验证各网站登录状态")
|
||||
for website in websites:
|
||||
self.process_website(
|
||||
result = self.process_website(
|
||||
user_name=user_name,
|
||||
website_config=website,
|
||||
cookie_cloud=cookie_cloud,
|
||||
notifier=notifier,
|
||||
max_fail_count=max_fail_count,
|
||||
browser=browser
|
||||
)
|
||||
finally:
|
||||
# 关闭浏览器
|
||||
browser.close()
|
||||
results.append(result)
|
||||
|
||||
self._log(f"用户 {user_name} 处理完成")
|
||||
if not result['success']:
|
||||
self.failure_tracker.check_and_notify(
|
||||
user_name=user_name,
|
||||
website_name=result['name'],
|
||||
max_fail_count=max_fail_count,
|
||||
notifier=notifier,
|
||||
error_msg=result['error']
|
||||
)
|
||||
else:
|
||||
self.failure_tracker.reset_fail_count(user_name, result['name'])
|
||||
|
||||
self._print_summary(user_name, results)
|
||||
|
||||
finally:
|
||||
browser.close()
|
||||
|
||||
def process_website(
|
||||
self,
|
||||
user_name: str,
|
||||
website_config: dict,
|
||||
cookie_cloud: CookieCloud,
|
||||
notifier: IYUUNotifier,
|
||||
max_fail_count: int,
|
||||
browser: BrowserLogin
|
||||
):
|
||||
) -> dict:
|
||||
"""
|
||||
处理单个网站
|
||||
|
||||
Args:
|
||||
user_name: 用户名
|
||||
website_config: 网站配置
|
||||
cookie_cloud: Cookie Cloud 客户端
|
||||
notifier: 爱语飞飞通知客户端
|
||||
max_fail_count: 最大失败次数
|
||||
browser: 浏览器客户端
|
||||
验证网站登录状态(cookies已预先导入)
|
||||
|
||||
Returns:
|
||||
dict: {'name': 网站名, 'url': URL, 'success': 是否成功, 'error': 错误信息}
|
||||
"""
|
||||
website_name = website_config.get('name', '未知网站')
|
||||
url = website_config.get('url', '')
|
||||
check_selector = website_config.get('login_check_selector', '')
|
||||
success_text = website_config.get('success_text', '')
|
||||
|
||||
self._log(f"处理网站: {user_name} - {website_name}")
|
||||
logger.info(f"\n验证网站: {website_name}")
|
||||
logger.info(f" URL: {url}")
|
||||
|
||||
# 获取域名
|
||||
domain = self._get_domain_from_url(url)
|
||||
result = {
|
||||
'name': website_name,
|
||||
'url': url,
|
||||
'success': False,
|
||||
'error': ''
|
||||
}
|
||||
|
||||
if not url:
|
||||
result['error'] = 'URL 未配置'
|
||||
logger.error(f" 失败: {result['error']}")
|
||||
return result
|
||||
|
||||
try:
|
||||
# 第一步:使用本地 cookie 登录
|
||||
self._log(f"尝试使用本地 cookie 登录: {website_name}")
|
||||
local_cookies = self.cookie_manager.get_cookies(user_name, website_name)
|
||||
|
||||
if local_cookies:
|
||||
login_success = browser.login_with_cookies(
|
||||
url=url,
|
||||
cookies=local_cookies,
|
||||
check_selector=check_selector,
|
||||
success_text=success_text
|
||||
)
|
||||
|
||||
if login_success:
|
||||
self._log(f"本地 cookie 登录成功: {website_name}")
|
||||
|
||||
# 刷新页面并保存新的 cookie
|
||||
new_cookies = browser.refresh_and_save_cookies(url)
|
||||
if new_cookies:
|
||||
self.cookie_manager.save_cookies(user_name, website_name, new_cookies)
|
||||
self._log(f"已更新 cookie: {website_name}")
|
||||
|
||||
# 重置失败计数
|
||||
self.failure_tracker.reset_fail_count(user_name, website_name)
|
||||
return
|
||||
|
||||
self._log(f"本地 cookie 登录失败: {website_name}")
|
||||
|
||||
# 第二步:从 Cookie Cloud 获取 cookie 并重试
|
||||
self._log(f"从 Cookie Cloud 获取 cookie: {website_name}")
|
||||
cloud_cookies = cookie_cloud.get_cookies_for_domain(domain)
|
||||
|
||||
if cloud_cookies:
|
||||
login_success = browser.login_with_cookies(
|
||||
url=url,
|
||||
cookies=cloud_cookies,
|
||||
check_selector=check_selector,
|
||||
success_text=success_text
|
||||
)
|
||||
|
||||
if login_success:
|
||||
self._log(f"Cookie Cloud 登录成功: {website_name}")
|
||||
|
||||
# 刷新页面并保存新的 cookie
|
||||
new_cookies = browser.refresh_and_save_cookies(url)
|
||||
if new_cookies:
|
||||
self.cookie_manager.save_cookies(user_name, website_name, new_cookies)
|
||||
self._log(f"已更新 cookie: {website_name}")
|
||||
|
||||
# 重置失败计数
|
||||
self.failure_tracker.reset_fail_count(user_name, website_name)
|
||||
return
|
||||
|
||||
self._log(f"Cookie Cloud 登录失败: {website_name}")
|
||||
else:
|
||||
self._log(f"未从 Cookie Cloud 获取到 cookie: {website_name}")
|
||||
|
||||
# 第三步:登录失败,增加失败计数并检查是否需要通知
|
||||
self._log(f"登录失败: {website_name}")
|
||||
error_msg = f"{website_name} 登录失败,请检查 Cookie Cloud 是否有最新的 cookie"
|
||||
|
||||
should_stop = self.failure_tracker.check_and_notify(
|
||||
user_name=user_name,
|
||||
website_name=website_name,
|
||||
max_fail_count=max_fail_count,
|
||||
notifier=notifier,
|
||||
error_msg=error_msg
|
||||
login_success = browser.verify_login(
|
||||
url=url,
|
||||
check_selector=check_selector,
|
||||
success_text=success_text
|
||||
)
|
||||
|
||||
if should_stop:
|
||||
self._log(f"已达到最大失败次数,已发送通知并重置计数: {website_name}")
|
||||
if login_success:
|
||||
result['success'] = True
|
||||
logger.info(f" ✓ 登录验证成功")
|
||||
|
||||
new_cookies = browser.refresh_and_save_cookies(url)
|
||||
if new_cookies:
|
||||
self.cookie_manager.save_cookies(user_name, website_name, new_cookies)
|
||||
logger.info(f" 已保存 {len(new_cookies)} 个新 cookie")
|
||||
else:
|
||||
fail_count = self.failure_tracker.get_fail_count(user_name, website_name)
|
||||
self._log(f"当前失败次数: {fail_count}/{max_fail_count}")
|
||||
result['error'] = '登录状态验证失败(cookies可能已过期或选择器配置错误)'
|
||||
logger.warning(f" ✗ {result['error']}")
|
||||
|
||||
except Exception as e:
|
||||
self._log(f"处理网站 {website_name} 时发生异常: {e}")
|
||||
error_msg = f"{website_name} 处理异常: {str(e)}"
|
||||
|
||||
should_stop = self.failure_tracker.check_and_notify(
|
||||
user_name=user_name,
|
||||
website_name=website_name,
|
||||
max_fail_count=max_fail_count,
|
||||
notifier=notifier,
|
||||
error_msg=error_msg
|
||||
)
|
||||
result['error'] = f'处理异常: {str(e)}'
|
||||
logger.error(f" 异常: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def _print_summary(self, user_name: str, results: list):
|
||||
"""打印处理结果汇总"""
|
||||
success_count = sum(1 for r in results if r['success'])
|
||||
fail_count = len(results) - success_count
|
||||
|
||||
logger.info(f"\n{'='*20} 用户 {user_name} 处理结果汇总 {'='*20}")
|
||||
logger.info(f"总网站数: {len(results)}")
|
||||
logger.info(f"成功: {success_count}, 失败: {fail_count}")
|
||||
|
||||
if fail_count > 0:
|
||||
logger.info("\n失败详情:")
|
||||
for r in results:
|
||||
if not r['success']:
|
||||
logger.info(f" - {r['name']}: {r['error']}")
|
||||
|
||||
def run(self):
|
||||
"""运行监控"""
|
||||
self._log("===== Cookie 监控开始 =====")
|
||||
logger.info("="*50)
|
||||
logger.info("Cookie 监控开始")
|
||||
logger.info(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
logger.info("="*50)
|
||||
|
||||
users = self.config.get('users', [])
|
||||
|
||||
if not users:
|
||||
self._log("未找到用户配置")
|
||||
logger.error("未找到用户配置")
|
||||
return
|
||||
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
for user_config in users:
|
||||
try:
|
||||
self.process_user(user_config)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
self._log(f"处理用户失败: {e}")
|
||||
fail_count += 1
|
||||
logger.error(f"处理用户失败: {e}")
|
||||
|
||||
self._log(f"===== Cookie 监控结束 =====")
|
||||
self._log(f"成功处理: {success_count} 个用户,失败: {fail_count} 个用户")
|
||||
logger.info("\n" + "="*50)
|
||||
logger.info("Cookie 监控结束")
|
||||
logger.info("="*50)
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 配置日志
|
||||
setup_logging()
|
||||
|
||||
monitor = CookieMonitor()
|
||||
monitor.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user