Initial commit

This commit is contained in:
2026-03-02 17:02:09 +08:00
commit f8a14a3e2e
16 changed files with 1845 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+62
View File
@@ -0,0 +1,62 @@
"""日志配置模块"""
import os
import logging
from logging.handlers import TimedRotatingFileHandler
def setup_logging(
log_dir: str = "log",
log_file: str = "app.log",
level: int = logging.INFO,
format_string: str = '%(asctime)s - %(levelname)s - %(message)s',
when: str = "H",
interval: int = 12,
backup_count: int = 7
) -> None:
"""
配置日志系统
Args:
log_dir: 日志目录
log_file: 日志文件名
level: 日志级别
format_string: 日志格式
when: 轮转时间单位 (S:秒, M:分, H:时, D:天, W:周)
interval: 轮转间隔
backup_count: 保留的备份文件数量
"""
# 确保日志目录存在
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 配置日志处理器
handlers = [
TimedRotatingFileHandler(
os.path.join(log_dir, log_file),
when=when,
interval=interval,
backupCount=backup_count,
encoding='utf-8'
),
logging.StreamHandler()
]
# 配置日志
logging.basicConfig(
level=level,
format=format_string,
handlers=handlers
)
def get_logger(name: str) -> logging.Logger:
"""
获取日志记录器
Args:
name: 日志记录器名称,通常使用 __name__
Returns:
Logger 实例
"""
return logging.getLogger(name)
+320
View File
@@ -0,0 +1,320 @@
"""浏览器登录模块 - 使用 DrissionPage 控制浏览器进行登录检测和 cookie 管理"""
from typing import Dict, List, Optional
import json
import time
from DrissionPage import Chromium, ChromiumOptions
from applogger import get_logger
logger = get_logger(__name__)
class BrowserLogin:
"""浏览器登录客户端"""
def __init__(self, browser_type: str = "edge", headless: bool = False):
"""
初始化浏览器登录客户端
Args:
browser_type: 浏览器类型 (chrome 或 edge)
headless: 是否无头模式
"""
self.browser_type = browser_type.lower()
self.headless = headless
self.browser = None
self.tab = None
def _create_browser(self):
"""创建浏览器实例"""
co = ChromiumOptions()
# 设置浏览器路径
if self.browser_type == "edge":
co.set_browser_path("C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe")
elif self.browser_type == "chrome":
co.set_browser_path("chrome")
# 设置无头模式
if self.headless:
co.headless = True
# 设置其他选项
co.set_argument('--no-sandbox')
co.set_argument('--disable-dev-shm-usage')
# 创建浏览器
self.browser = Chromium(co)
self.tab = self.browser.new_tab()
def _close_browser(self):
"""关闭浏览器"""
if self.browser:
try:
self.browser.quit()
except Exception as e:
logger.error(f"关闭浏览器失败: {e}")
finally:
self.browser = None
self.tab = None
def _get_domain_from_url(self, url: str) -> str:
"""
从 URL 中提取域名
Args:
url: 网站地址
Returns:
域名
"""
from urllib.parse import urlparse
parsed = urlparse(url)
return parsed.netloc
def _get_cookies_dict(self) -> Dict[str, List[Dict]]:
"""
获取当前浏览器的所有 cookie,按域名分组
Returns:
按域名分组的 cookie 字典
"""
cookies = self.tab.cookies.as_dict
# 按域名分组
cookie_dict = {}
for cookie in cookies:
domain = cookie.get('domain', '')
# 统一域名格式(去掉开头的点)
if domain.startswith('.'):
domain = domain[1:]
if domain not in cookie_dict:
cookie_dict[domain] = []
cookie_dict[domain].append(cookie)
return cookie_dict
def check_login(self, url: str, check_selector: str, success_text: str) -> bool:
"""
检查是否已登录
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(2)
# 检查登录状态
element = self.tab.ele(check_selector, timeout=10)
if element and element.text and success_text in element.text:
return True
return False
except Exception as e:
logger.error(f"检查登录状态失败: {e}")
return False
def login_with_cookies(
self,
url: str,
cookies: List[Dict],
check_selector: str,
success_text: str
) -> bool:
"""
使用 cookie 登录
Args:
url: 网站地址
cookies: cookie 列表
check_selector: 登录检测选择器
success_text: 登录成功时显示的文本
Returns:
是否登录成功
"""
if not self.browser or not self.tab:
self._create_browser()
try:
# 访问网站
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:
return True
return False
except Exception as e:
logger.error(f"使用 cookie 登录失败: {e}")
return False
def refresh_and_save_cookies(self, url: str) -> List[Dict]:
"""
刷新页面并保存新的 cookie
Args:
url: 网站地址
Returns:
cookie 列表
"""
if not self.browser or not self.tab:
self._create_browser()
try:
# 刷新页面
self.tab.refresh()
# 等待页面加载
time.sleep(3)
# 获取 cookies
cookies = self.tab.cookies.as_dict
return cookies
except Exception as e:
logger.error(f"刷新页面并保存 cookie 失败: {e}")
return []
def close(self):
"""关闭浏览器"""
self._close_browser()
class CookieManager:
"""Cookie 管理器"""
def __init__(self, cookie_file: str):
"""
初始化 Cookie 管理器
Args:
cookie_file: cookie 文件路径
"""
self.cookie_file = cookie_file
self.cookies = self._load_cookies()
def _load_cookies(self) -> Dict[str, Dict[str, List[Dict]]]:
"""加载 cookie 文件"""
import os
if os.path.exists(self.cookie_file):
try:
with open(self.cookie_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"加载 cookie 文件失败: {e}")
return {}
def _save_cookies(self):
"""保存 cookie 文件"""
try:
with open(self.cookie_file, 'w', encoding='utf-8') as f:
json.dump(self.cookies, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error(f"保存 cookie 文件失败: {e}")
def get_cookies(self, user_name: str, website_name: str) -> List[Dict]:
"""
获取指定用户和网站的 cookie
Args:
user_name: 用户名
website_name: 网站名
Returns:
cookie 列表
"""
if user_name not in self.cookies:
return []
if website_name not in self.cookies[user_name]:
return []
return self.cookies[user_name][website_name]
def save_cookies(self, user_name: str, website_name: str, cookies: List[Dict]):
"""
保存指定用户和网站的 cookie
Args:
user_name: 用户名
website_name: 网站名
cookies: cookie 列表
"""
if user_name not in self.cookies:
self.cookies[user_name] = {}
self.cookies[user_name][website_name] = cookies
self._save_cookies()
def get_cookies_for_domain(self, domain: str) -> List[Dict]:
"""
获取指定域名的 cookie(从所有用户的 cookie 中查找)
Args:
domain: 域名
Returns:
cookie 列表
"""
all_cookies = []
for user_name, websites in self.cookies.items():
for website_name, cookies in websites.items():
for cookie in cookies:
cookie_domain = cookie.get('domain', '')
# 统一域名格式
if cookie_domain.startswith('.'):
cookie_domain = cookie_domain[1:]
if domain in cookie_domain or cookie_domain in domain:
all_cookies.append(cookie)
return all_cookies
if __name__ == "__main__":
# 测试代码
manager = CookieManager("cookies.json")
# 保存测试 cookie
test_cookies = [
{"name": "test", "value": "123", "domain": "example.com", "path": "/"}
]
manager.save_cookies("测试用户", "测试网站", test_cookies)
# 获取 cookie
cookies = manager.get_cookies("测试用户", "测试网站")
logger.info(f"获取到的 cookie: {cookies}")
+1
View File
@@ -0,0 +1 @@
{"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": "彭峰"}]}]}
+183
View File
@@ -0,0 +1,183 @@
"""Cookie Cloud 模块 - 从 Cookie Cloud 服务器获取并解密 cookie 数据"""
import hashlib
import json
import base64
from typing import Dict, List, Optional
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from applogger import get_logger
logger = get_logger(__name__)
class CookieCloud:
"""Cookie Cloud 客户端"""
def __init__(self, api_url: str, uuid: str, password: str):
"""
初始化 Cookie Cloud 客户端
Args:
api_url: Cookie Cloud API 地址
uuid: 用户 UUID
password: 用户密码
"""
self.api_url = api_url.rstrip('/')
self.uuid = uuid
self.password = password
def decrypt(self, encrypted_data: str) -> Dict:
"""
解密 Cookie Cloud 数据
Args:
encrypted_data: 加密的数据字符串
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]}")
try:
# 移除 PKCS#7 填充
decrypted = unpad(decrypted, AES.block_size)
print(f"移除填充后长度: {len(decrypted)}")
print(f"移除填充后前 100 字节: {decrypted[:100]}")
# 尝试解析 JSON
result = json.loads(decrypted.decode('utf-8'))
print("JSON 解析成功")
return result
except Exception as e:
print(f"解密失败: {e}")
# 尝试直接截取可能的有效数据
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
def get_cookies(self) -> Dict[str, List[Dict]]:
"""
从 Cookie Cloud 服务器获取 cookie 数据
Returns:
按域名分组的 cookie 数据字典
格式: {"domain.com": [{"name": "cookie1", "value": "val1", ...}, ...], ...}
"""
url = f"{self.api_url}/get/{self.uuid}"
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}")
return {}
def get_cookies_for_domain(self, domain: str) -> List[Dict]:
"""
获取指定域名的 cookie
Args:
domain: 域名
Returns:
cookie 列表
"""
all_cookies = self.get_cookies()
# 查找匹配的域名
for cookie_domain, cookies in all_cookies.items():
if domain in cookie_domain or cookie_domain in domain:
return cookies
return []
if __name__ == "__main__":
# 测试代码
cc = CookieCloud(
api_url="https://movie-pilot.org/cookiecloud",
uuid="hu6n2vcUmzpu7mqUN2rVCg",
password="jtDM5dV9AyqXkZdQVeA9f6"
)
cookies = cc.get_cookies()
logger.info(json.dumps(cookies, indent=2, ensure_ascii=False))
+1
View File
@@ -0,0 +1 @@
{}
+345
View File
File diff suppressed because one or more lines are too long
+251
View File
@@ -0,0 +1,251 @@
"""Cookie 监控主程序"""
import json
import os
import logging
from datetime import datetime
from cookie_cloud import CookieCloud
from notifier import IYUUNotifier, FailureTracker
from browser_login import BrowserLogin, CookieManager
from applogger import setup_logging, get_logger
logger = get_logger(__name__)
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}")
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
def process_user(self, user_config: dict):
"""
处理单个用户的所有网站
Args:
user_config: 用户配置
"""
user_name = user_config.get('name', '未知用户')
self._log(f"开始处理用户: {user_name}")
# 获取用户配置
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', [])
# 初始化爱语飞飞通知
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', '')
)
# 初始化浏览器
browser = BrowserLogin(
browser_type=browser_config.get('type', 'edge'),
headless=browser_config.get('headless', False)
)
try:
# 处理每个网站
for website in websites:
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()
self._log(f"用户 {user_name} 处理完成")
def process_website(
self,
user_name: str,
website_config: dict,
cookie_cloud: CookieCloud,
notifier: IYUUNotifier,
max_fail_count: int,
browser: BrowserLogin
):
"""
处理单个网站
Args:
user_name: 用户名
website_config: 网站配置
cookie_cloud: Cookie Cloud 客户端
notifier: 爱语飞飞通知客户端
max_fail_count: 最大失败次数
browser: 浏览器客户端
"""
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}")
# 获取域名
domain = self._get_domain_from_url(url)
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
)
if should_stop:
self._log(f"已达到最大失败次数,已发送通知并重置计数: {website_name}")
else:
fail_count = self.failure_tracker.get_fail_count(user_name, website_name)
self._log(f"当前失败次数: {fail_count}/{max_fail_count}")
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
)
def run(self):
"""运行监控"""
self._log("===== Cookie 监控开始 =====")
users = self.config.get('users', [])
if not users:
self._log("未找到用户配置")
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
self._log(f"===== Cookie 监控结束 =====")
self._log(f"成功处理: {success_count} 个用户,失败: {fail_count} 个用户")
def main():
"""主函数"""
# 配置日志
setup_logging()
monitor = CookieMonitor()
monitor.run()
if __name__ == "__main__":
main()
+189
View File
@@ -0,0 +1,189 @@
"""消息通知模块 - 通过爱语飞飞发送通知"""
import requests
from typing import Optional
from applogger import get_logger
logger = get_logger(__name__)
class IYUUNotifier:
"""爱语飞飞通知客户端"""
def __init__(self, token: str):
"""
初始化爱语飞飞通知客户端
Args:
token: 爱语飞飞令牌
"""
self.token = token
self.api_url = f"https://iyuu.cn/{token}.send"
def send(self, title: str, content: str) -> bool:
"""
发送通知
Args:
title: 通知标题
content: 通知内容
Returns:
是否发送成功
"""
try:
response = requests.get(
self.api_url,
params={
'text': title,
'desp': content
},
timeout=10
)
response.raise_for_status()
result = response.json()
if result.get('errcode') == 0:
return True
else:
logger.error(f"发送通知失败: {result.get('errmsg')}")
return False
except requests.RequestException as e:
logger.error(f"发送通知请求失败: {e}")
return False
except Exception as e:
logger.error(f"发送通知异常: {e}")
return False
class FailureTracker:
"""失败次数追踪器"""
def __init__(self, state_file: str):
"""
初始化失败次数追踪器
Args:
state_file: 状态文件路径
"""
self.state_file = state_file
self.state = self._load_state()
def _load_state(self) -> dict:
"""加载状态文件"""
import json
import os
if os.path.exists(self.state_file):
try:
with open(self.state_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"加载状态文件失败: {e}")
return {}
def _save_state(self):
"""保存状态文件"""
import json
try:
with open(self.state_file, 'w', encoding='utf-8') as f:
json.dump(self.state, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error(f"保存状态文件失败: {e}")
def get_fail_count(self, user_name: str, website_name: str) -> int:
"""
获取指定网站的失败次数
Args:
user_name: 用户名
website_name: 网站名
Returns:
失败次数
"""
key = f"{user_name}_{website_name}"
return self.state.get(key, 0)
def increment_fail_count(self, user_name: str, website_name: str) -> int:
"""
增加失败次数
Args:
user_name: 用户名
website_name: 网站名
Returns:
增加后的失败次数
"""
key = f"{user_name}_{website_name}"
self.state[key] = self.state.get(key, 0) + 1
self._save_state()
return self.state[key]
def reset_fail_count(self, user_name: str, website_name: str):
"""
重置失败次数
Args:
user_name: 用户名
website_name: 网站名
"""
key = f"{user_name}_{website_name}"
self.state[key] = 0
self._save_state()
def check_and_notify(
self,
user_name: str,
website_name: str,
max_fail_count: int,
notifier: Optional[IYUUNotifier],
error_msg: str
) -> bool:
"""
检查失败次数并在达到阈值时发送通知
Args:
user_name: 用户名
website_name: 网站名
max_fail_count: 最大失败次数
notifier: 爱语飞飞通知客户端
error_msg: 错误信息
Returns:
是否应该停止重试
"""
fail_count = self.increment_fail_count(user_name, website_name)
if fail_count >= max_fail_count:
# 发送通知
if notifier:
title = f"【Cookie监控】{user_name} - {website_name} 登录失败"
content = f"网站: {website_name}\n用户: {user_name}\n连续失败次数: {fail_count}\n错误信息: {error_msg}"
if notifier.send(title, content):
logger.info(f"已发送失败通知: {user_name} - {website_name}")
else:
logger.error(f"发送失败通知失败: {user_name} - {website_name}")
# 重置失败计数
self.reset_fail_count(user_name, website_name)
return True
return False
if __name__ == "__main__":
# 测试代码
tracker = FailureTracker("state.json")
# 增加失败次数
count = tracker.increment_fail_count("测试用户", "测试网站")
logger.info(f"当前失败次数: {count}")
# 检查并发送通知(需要真实的爱语飞飞令牌)
# notifier = IYUUNotifier("您的爱语飞飞令牌")
# tracker.check_and_notify("测试用户", "测试网站", 3, notifier, "测试错误信息")
+3
View File
@@ -0,0 +1,3 @@
DrissionPage
requests
pycryptodome
+3
View File
@@ -0,0 +1,3 @@
{
"用户A_网站A": 2
}
+314
View File
@@ -0,0 +1,314 @@
# Cookie 自动监控功能需求文档
## 1. 功能概述
本系统提供 Cookie 自动监控、管理和通知功能,确保用户能够持续访问多个网站。
## 2. 详细功能需求
### 2.1 Cookie Cloud 集成
#### 2.1.1 从 Cookie Cloud 获取 Cookie
- **功能描述**: 从 Cookie Cloud 服务器获取并解密 Cookie 数据
- **输入**: Cookie Cloud API 地址、UUID、密码
- **输出**: 按域名分组的 Cookie 数据
- **处理逻辑**:
1. 发送 GET 请求到 `/get/{uuid}` 接口
2. 获取加密的 Cookie 数据
3. 使用 AES 算法解密数据
4. 解析 JSON 格式的 Cookie 数据
5. 按域名组织 Cookie
#### 2.1.2 按域名获取 Cookie
- **功能描述**: 获取指定域名的 Cookie
- **输入**: 域名
- **输出**: 该域名的 Cookie 列表
- **处理逻辑**: 遍历所有 Cookie,匹配域名
### 2.2 浏览器控制
#### 2.2.1 创建浏览器实例
- **功能描述**: 创建浏览器实例并启动浏览器
- **输入**: 浏览器类型(chrome/edge)、是否无头模式
- **输出**: 浏览器对象
- **支持浏览器**: Chrome、Edge
- **运行模式**: 有头模式、无头模式
#### 2.2.2 检测登录状态
- **功能描述**: 检测网站是否已登录
- **输入**: 网站 URL、登录检测选择器、成功文本
- **输出**: 是否已登录(True/False
- **处理逻辑**:
1. 访问网站
2. 查找登录检测元素
3. 检查元素文本是否包含成功文本
#### 2.2.3 使用 Cookie 登录
- **功能描述**: 使用 Cookie 登录网站
- **输入**: 网站 URL、Cookie 列表、登录检测选择器、成功文本
- **输出**: 是否登录成功(True/False
- **处理逻辑**:
1. 访问网站
2. 设置 Cookie
3. 刷新页面
4. 检测登录状态
#### 2.2.4 刷新并保存 Cookie
- **功能描述**: 刷新页面并获取最新的 Cookie
- **输入**: 网站 URL
- **输出**: 最新的 Cookie 列表
- **处理逻辑**:
1. 刷新页面
2. 获取当前所有 Cookie
3. 返回 Cookie 列表
### 2.3 Cookie 管理
#### 2.3.1 保存 Cookie
- **功能描述**: 保存指定用户和网站的 Cookie
- **输入**: 用户名、网站名、Cookie 列表
- **输出**: 无
- **存储位置**: `cookies.json` 文件
- **存储格式**:
```json
{
"用户名": {
"网站名": [
{
"name": "cookie名称",
"value": "cookie值",
"domain": "域名",
"path": "路径",
"expiry": "过期时间"
}
]
}
}
```
#### 2.3.2 读取 Cookie
- **功能描述**: 读取指定用户和网站的 Cookie
- **输入**: 用户名、网站名
- **输出**: Cookie 列表
- **数据来源**: `cookies.json` 文件
#### 2.3.3 按域名获取 Cookie
- **功能描述**: 从所有 Cookie 中查找指定域名的 Cookie
- **输入**: 域名
- **输出**: Cookie 列表
- **匹配规则**: 域名完全匹配或包含关系
### 2.4 失败追踪
#### 2.4.1 获取失败次数
- **功能描述**: 获取指定用户和网站的失败次数
- **输入**: 用户名、网站名
- **输出**: 失败次数
- **数据来源**: `state.json` 文件
#### 2.4.2 增加失败次数
- **功能描述**: 增加指定用户和网站的失败次数
- **输入**: 用户名、网站名
- **输出**: 增加后的失败次数
- **存储位置**: `state.json` 文件
#### 2.4.3 重置失败次数
- **功能描述**: 重置指定用户和网站的失败次数
- **输入**: 用户名、网站名
- **输出**: 无
#### 2.4.4 检查并发送通知
- **功能描述**: 检查失败次数,达到阈值时发送通知
- **输入**: 用户名、网站名、最大失败次数、通知器、错误信息
- **输出**: 是否应该停止重试
- **处理逻辑**:
1. 增加失败次数
2. 判断是否达到阈值
3. 达到阈值则发送通知
4. 发送通知后重置失败计数
5. 返回是否停止重试
### 2.5 消息通知
#### 2.5.1 发送爱语飞飞通知
- **功能描述**: 通过爱语飞飞 API 发送通知
- **输入**: 令牌、标题、内容
- **输出**: 是否发送成功(True/False
- **API 地址**: `https://iyuu.cn/{令牌}.send`
- **请求方式**: GET
- **请求参数**:
- `text`: 通知标题
- `desp`: 通知内容
### 2.6 主程序流程
#### 2.6.1 读取配置
- **功能描述**: 读取配置文件
- **输入**: 配置文件路径
- **输出**: 配置字典
- **配置文件**: `config.json`
#### 2.6.2 处理用户
- **功能描述**: 处理单个用户的所有网站
- **输入**: 用户配置
- **输出**: 无
- **处理逻辑**:
1. 初始化 Cookie Cloud 客户端
2. 初始化爱语飞飞通知器
3. 初始化浏览器
4. 遍历该用户的所有网站
5. 调用处理网站功能
6. 关闭浏览器
#### 2.6.3 处理网站
- **功能描述**: 处理单个网站的监控
- **输入**: 用户名、网站配置、Cookie Cloud 客户端、通知器、最大失败次数、浏览器
- **输出**: 无
- **处理流程**:
1. **步骤 1**: 尝试使用本地 Cookie 登录
- 读取本地 Cookie
- 登录网站
- 检测登录状态
- 登录成功 → 跳转到步骤 3
- 登录失败 → 跳转到步骤 2
2. **步骤 2**: 从 Cookie Cloud 获取 Cookie 并重试
- 从 Cookie Cloud 获取 Cookie
- 登录网站
- 检测登录状态
- 登录成功 → 跳转到步骤 3
- 登录失败 → 跳转到步骤 4
3. **步骤 3**: 刷新页面并保存新 Cookie
- 刷新页面
- 获取最新 Cookie
- 保存到 Cookie 文件
- 重置失败计数
- 结束
4. **步骤 4**: 登录失败处理
- 增加失败计数
- 判断是否达到阈值
- 达到阈值 → 发送通知 → 重置计数
- 未达到阈值 → 记录日志
#### 2.6.4 运行监控
- **功能描述**: 运行整个监控流程
- **输入**: 无
- **输出**: 无
- **处理逻辑**:
1. 读取配置文件
2. 遍历所有用户
3. 处理每个用户
4. 记录成功和失败数量
5. 输出日志
## 3. 配置管理功能
### 3.1 配置文件管理
- 支持多用户配置
- 支持多网站配置
- 支持 Cookie Cloud 配置
- 支持通知配置
- 支持浏览器配置
### 3.2 状态文件管理
- 记录失败次数
- 持久化存储
- 自动加载和保存
### 3.3 Cookie 文件管理
- 统一管理所有 Cookie
- 按用户和网站组织
- 持久化存储
## 4. 日志功能
### 4.1 日志记录
- 记录操作开始和结束
- 记录用户和网站处理状态
- 记录登录成功和失败
- 记录 Cookie 更新
- 记录通知发送
- 记录异常信息
### 4.2 日志格式
```
[时间戳] 日志内容
```
## 5. 异常处理
### 5.1 网络异常
- Cookie Cloud 请求失败
- 网站访问失败
- 通知发送失败
### 5.2 浏览器异常
- 浏览器启动失败
- 元素查找失败
- Cookie 设置失败
### 5.3 文件异常
- 配置文件读取失败
- Cookie 文件读写失败
- 状态文件读写失败
### 5.4 加密异常
- Cookie 解密失败
- 密钥生成失败
## 6. 性能要求
- Cookie Cloud 请求超时:30 秒
- 网站访问超时:10 秒
- 元素查找超时:10 秒
- 通知发送超时:10 秒
## 7. 安全要求
- Cookie Cloud 使用端到端加密
- 配置文件包含敏感信息,需妥善保管
- 爱语飞飞令牌不应泄露
- Cookie 数据不应明文传输
## 8. 扩展功能
### 8.1 多用户支持
- 每个用户独立的 Cookie Cloud 配置
- 每个用户独立的爱语飞飞令牌
- 每个用户独立的浏览器配置
### 8.2 多网站支持
- 每个用户可以配置多个网站
- 每个网站独立的登录检测
- 每个网站独立的失败计数
### 8.3 浏览器配置
- 支持 Chrome 和 Edge
- 支持有头和无头模式
- 每个用户可以独立配置浏览器
### 8.4 通知配置
- 支持自定义最大失败次数
- 支持自定义通知内容
- 每个用户独立的通知配置
## 9. 非功能需求
### 9.1 可靠性
- 程序异常不影响下次执行
- 状态文件确保数据不丢失
- 浏览器异常自动关闭
### 9.2 可维护性
- 模块化设计
- 清晰的日志输出
- 详细的错误信息
### 9.3 可扩展性
- 易于添加新用户
- 易于添加新网站
- 易于添加新通知方式
### 9.4 易用性
- 配置文件简单明了
- 日志输出清晰易懂
- 通知信息详细准确
+173
View File
@@ -0,0 +1,173 @@
# Cookie 自动监控项目需求文档
## 1. 项目背景
本项目旨在实现一个自动化 Cookie 管理和监控系统,通过定时任务检查多个网站的登录状态,自动刷新和更新 Cookie,确保用户能够持续访问目标网站。当登录失败时,系统会自动从 Cookie Cloud 服务器获取最新的 Cookie 进行重试,并在连续失败达到阈值时通过爱语飞飞消息服务发送通知。
## 2. 项目目标
- 自动监控多个网站的登录状态
- 自动刷新和更新 Cookie
- 支持 Cookie Cloud 备份和恢复
- 支持多用户、多网站配置
- 支持多种浏览器(Chrome、Edge
- 支持有头/无头模式切换
- 失败通知机制,避免消息过于频繁
## 3. 技术栈
- **编程语言**: Python 3.14.2
- **浏览器控制**: DrissionPage
- **HTTP 请求**: requests
- **加密解密**: pycryptodome
- **消息通知**: 爱语飞飞 API
- **定时任务**: Windows 任务计划程序
- **配置格式**: JSON
## 4. 运行环境
- **操作系统**: Windows 10/11
- **Python 版本**: 3.14.2
- **浏览器**: Chrome 或 Edge(系统自带)
## 5. 系统架构
### 5.1 模块划分
1. **Cookie Cloud 模块** (`cookie_cloud.py`)
- 从 Cookie Cloud 服务器获取 Cookie 数据
- 解密 Cookie 数据
- 按域名组织 Cookie
2. **消息通知模块** (`notifier.py`)
- 集成爱语飞飞 API
- 失败次数追踪
- 通知发送控制
3. **浏览器登录模块** (`browser_login.py`)
- 使用 DrissionPage 控制浏览器
- 检测登录状态
- 管理 Cookie
4. **Cookie 管理器** (`browser_login.py`)
- 统一管理所有 Cookie
- 支持按用户和网站存储
- 持久化存储
5. **主程序** (`monitor.py`)
- 读取配置文件
- 协调各模块工作
- 处理异常和日志
### 5.2 数据流
```
配置文件 → 主程序 → 浏览器登录模块 → 网站检测
Cookie 管理器 ← Cookie Cloud 模块
消息通知模块 ← 失败追踪器
```
## 6. 核心流程
### 6.1 监控流程
1. 读取配置文件
2. 遍历每个用户
3. 遍历该用户的每个网站
4. 尝试使用本地 Cookie 登录
5. 登录失败 → 从 Cookie Cloud 获取 Cookie → 重试登录
6. 登录成功 → 刷新页面 → 保存新 Cookie → 重置失败计数
7. 登录失败 → 失败计数 +1
8. 失败计数达到阈值 → 发送提醒 → 重置计数
9. 保存状态到状态文件
### 6.2 失败重试策略
- 每个网站独立计数失败次数
- 连续失败 3 次后发送通知
- 发送通知后重置计数
- 下次执行重新开始计数
## 7. 配置管理
### 7.1 配置文件结构
```json
{
"users": [
{
"name": "用户名",
"cookie_cloud": {
"uuid": "Cookie Cloud UUID",
"password": "Cookie Cloud 密码",
"api_url": "Cookie Cloud API 地址"
},
"notification": {
"iyuu_token": "爱语飞飞令牌",
"max_fail_count": 3
},
"browser": {
"type": "edge/chrome",
"headless": true/false
},
"websites": [
{
"name": "网站名称",
"url": "网站地址",
"login_check_selector": "登录检测选择器",
"success_text": "登录成功文本"
}
]
}
]
}
```
### 7.2 数据文件
- `config.json`: 配置文件
- `cookies.json`: Cookie 存储文件
- `state.json`: 失败计数状态文件
## 8. 定时任务
使用 Windows 任务计划程序设置定时任务:
- 执行频率:每 30 分钟
- 执行命令:`python G:\test\cookie\monitor.py`
- 运行账户:SYSTEM
## 9. 依赖安装
```bash
pip install DrissionPage requests pycryptodome
```
## 10. 项目文件结构
```
G:\test\cookie\
├── config.json # 配置文件
├── monitor.py # 主程序
├── cookie_cloud.py # Cookie Cloud 模块
├── notifier.py # 消息通知模块
├── browser_login.py # 浏览器登录模块
├── state.json # 失败计数状态文件
└── cookies.json # Cookie 存储文件
```
## 11. 安全考虑
- Cookie Cloud 使用端到端加密
- 配置文件包含敏感信息,需妥善保管
- 定时任务使用 SYSTEM 账户运行,需谨慎授权
- 爱语飞飞令牌不应泄露
## 12. 扩展性
- 支持添加更多用户
- 支持添加更多网站
- 支持自定义失败阈值
- 支持自定义通知消息内容
- 支持切换浏览器类型和运行模式