From 894acc3191928b12407ed2a285a4af10d0684420 Mon Sep 17 00:00:00 2001 From: bwstudio <7469504@qq.com> Date: Tue, 3 Mar 2026 08:58:16 +0800 Subject: [PATCH] add-files --- __pycache__/applogger.cpython-310.pyc | Bin 0 -> 1533 bytes __pycache__/browser_login.cpython-310.pyc | Bin 0 -> 10959 bytes __pycache__/cookie_cloud.cpython-310.pyc | Bin 0 -> 5267 bytes __pycache__/notifier.cpython-310.pyc | Bin 0 -> 5392 bytes cookiecloud_client/README.md | 248 +++++++ cookiecloud_client/__init__.py | 33 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 727 bytes .../__pycache__/client.cpython-310.pyc | Bin 0 -> 9180 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 2743 bytes .../__pycache__/models.cpython-310.pyc | Bin 0 -> 4450 bytes .../__pycache__/tests.cpython-310.pyc | Bin 0 -> 9147 bytes .../__pycache__/version.cpython-310.pyc | Bin 0 -> 306 bytes cookiecloud_client/client.py | 347 ++++++++++ cookiecloud_client/examples.py | 259 +++++++ cookiecloud_client/exceptions.py | 65 ++ cookiecloud_client/models.py | 126 ++++ cookiecloud_client/requirements.txt | 14 + cookiecloud_client/setup.py | 54 ++ cookiecloud_client/tests.py | 295 ++++++++ cookiecloud_client/version.py | 8 + log/app.log | 647 ++++++++++++++++++ moviepilot_cookie_demo.py | 394 +++++++++++ state.json | 3 +- 23 files changed, 2492 insertions(+), 1 deletion(-) create mode 100644 __pycache__/applogger.cpython-310.pyc create mode 100644 __pycache__/browser_login.cpython-310.pyc create mode 100644 __pycache__/cookie_cloud.cpython-310.pyc create mode 100644 __pycache__/notifier.cpython-310.pyc create mode 100644 cookiecloud_client/README.md create mode 100644 cookiecloud_client/__init__.py create mode 100644 cookiecloud_client/__pycache__/__init__.cpython-310.pyc create mode 100644 cookiecloud_client/__pycache__/client.cpython-310.pyc create mode 100644 cookiecloud_client/__pycache__/exceptions.cpython-310.pyc create mode 100644 cookiecloud_client/__pycache__/models.cpython-310.pyc create mode 100644 cookiecloud_client/__pycache__/tests.cpython-310.pyc create mode 100644 cookiecloud_client/__pycache__/version.cpython-310.pyc create mode 100644 cookiecloud_client/client.py create mode 100644 cookiecloud_client/examples.py create mode 100644 cookiecloud_client/exceptions.py create mode 100644 cookiecloud_client/models.py create mode 100644 cookiecloud_client/requirements.txt create mode 100644 cookiecloud_client/setup.py create mode 100644 cookiecloud_client/tests.py create mode 100644 cookiecloud_client/version.py create mode 100644 moviepilot_cookie_demo.py diff --git a/__pycache__/applogger.cpython-310.pyc b/__pycache__/applogger.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d194fe4947d94ebdfb87469857c993f9e3bde4f GIT binary patch literal 1533 zcmZux&2Jk;6yKfw%-W_&fwtm9?4=?rB{^|GD?n7Ks#Jm?HAqNSq}A3lb~drS*37zv z${-GbR)8s>rdERriPS2oKv5)6sYu-XA9LYA){akH`$2&>yRnl>%xY%doA>6unfLq6 zhPj+2LHlI>cJPNLNq-9Ea2o;TZRjk50ZVxa%NUjAyqwG^k6>2HN=419vV>Kv5j3x1 z9UIuhLq!8yM8_HESz_QE%+EkS0`f5Q5sZq%_$YpMQOle7Ik1et?xCWJpU1}*)x3q# zjN}}zABEJN*6nzw)%&d3ZEtkeSL2(l9;`~!j?7QIA5;iF7e;;*R7+=qGMVzLxJ)Ql z%3E>HEc5WQ!>zzx{!;ec{e z2t|m`wPY*L6+>F_BacNCGIRCf9I0|Ms78cd@XI`V-Y+cFYhEF&S0k=d64hz7AY%5L z34+ikp|fciB57V;K@DkDW|>G@QG_H*k(#V)4XGioqK4Yg8oH=|Q;YN!V^u1lkBvy5 zHyfy-Gz_dX6@dlRPOHBuOOj;G#_lg$-K`(3Wc?H^F*lKf*Zx&qJFR#3`;EB0 z^psWHzi;kbzr4G3H~yx1;2}!dH+O$ui?94Nusv0*zE5XmJKnxJ@DUYs?Y;KKUi;@x z>u#_0YusGgZ8z=F8MnLk-I)EZ8(;Z+%${<=I%c1C<6C#e><`@d+jVCUqz^zp5Jc%M zUmKVYqvG1TJ2$&aH@eG<@h!MaJH1k8X|s1ZJ=G!5cT}1Ie5jyBK9h!+OmHU3c$BP6S4w5p|?0BP+At025QRnFJK`7W$)|X;;YwF#_erv0^{Nv z>;K12k)ThUfg{kbySDk)_E)`6m*ac)cH2AMYwNb>31`ns!<u!o{|{Qa;)4JH literal 0 HcmV?d00001 diff --git a/__pycache__/browser_login.cpython-310.pyc b/__pycache__/browser_login.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88714ce52ffdb799d951c4bd7d1cd819cabf223e GIT binary patch literal 10959 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 D<{Hht literal 0 HcmV?d00001 diff --git a/__pycache__/notifier.cpython-310.pyc b/__pycache__/notifier.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c37e7999e38c8854d26c935d3a9373f16e64e7c GIT binary patch literal 5392 zcmcIo`;!#a74GLu&tr#`@RArEqd_N8Ec2)`8)7gj!AfdVB^Zh+swTtSUhLi3nelda zmYtfV7A0XtK&YUCg3h1>h=Elr0SvDEmOr9@m7bmb6M|9mo!dJ-k6p>E92eZE?fKBGqWqoJ`GuU$jIq^!hbrWEC@j9;~=jH_8yrarBx zjq6!mmQ3A@yd-C%zmk~FA_pWEc}d9{XhoTUmVs8xjIh|4!Z(>Q7Kd!yjMoz^!IBLn zn>3^K0XASJ-;&NK4H;`I+r*nmxj(`dnpb9;`)3!X-<*HvT=PQ1Kl4WV;WV198wclS zK5WfiT6p(u{Q0jQUzpw>h%I26G@Ow=&A)s&l&TIs z?>H5E=#fXPN!Q(2C_PfuH`->24QCXtV=rDbU$Js7FJ=@^v(4g&=)EX$1B16VJT_?@xw^Vrvm4to zP@&}%O)pv~mmIU?coA+oE-w{GhwX=eC36=#{TC|Lha71XdL31uT|j(VVSx9DI;Gas zy4sLr3BOuHn$l`o9Y)@enGVCfX9vuBo8;*A2#bmlqXAR`gT+{Eo1{rtIXbyjmZsno z)fGZEUeh~LJ0+Hw()Z~k9n>ad%rYi-_smKDuQ?M^X27k}$rcSD-EI80MVd4=MBYQr zR6n?I_GGvQt+lAiwuJwuKl+zH?kxDMP;j61G*8a@$Nm!RpgD8QKlDzxBpHKa?sc*n zGwsR(0fDYaCC~HYwik7*akK0?Uex5g0Gr@41h%`YP<9{4fA7QA-_H%Dy$IyS?LAxE z^?eep*{jVDUu??_FJ0l27yWD3hSHgsXK?cc*R&nmQ^5zE?86)6dCN9)BW0emoxJ1P zo;GTiOI{>k3s1*)%iJ4$HkkVK-hwG;#fyd2OoT_V6i-k=)=M91luBJvu%gUX?crG_yW^R${$z1LQH(y*FiRN;wTtK&KJDd|Lp0>MOF7kU&?b;*x zE~kxhaz(1x)MZE-_O(7PdJE|2dFb$)@!BU*+?GlsAXa#jpirVxDQmDe$TfjiH)9~! zBpbkJystOKR-iZ0?j6zCAiD!S$#xH0iEHLt=75>HQ(~*cYAZ&PY&Ba0sX?|D47$!s zZOL0jmz&#pzAy&D##JZ<7S#M(qxsqgts8T#Yk&1mUGU;$6fh5~XfEMv;%vb!WV8Wa z*QC1KkigYo7ko)^M==z-M99_4eyihZOIf=>#yD7Q4wpT+T@$vzP706fH58VJX zUufbqDk5Z%rYK}U=rET?}dtb0hK z)Cj6fT)=em#u@+8$>m_0;6K2o`9mlc$PKs9)nb(xg#t;lWW%>}dAnd)0=uQ_gxDDZ zJN_uGCOk)D?s$b~sNIK_)`b?urprME$FuW)FayW?9fWK$Hv_!BQeGo(_$sB`4I)>* zS_)KWniq3zlM5#Ik`v}`+X4X*d-nu4gdn}b?Z_@fpop*rx}|_d8U+>Apw|X8n^E15 zAQJLK>*!Vg)z_Pc5BitioS!>B|Ng1@HxG1^((Sk?+#RUs#07-3{;?zB$`nX+?-?>; zeUb@K{cAxe32@MFfWa(N`}$B=Zq^bkR4gNG{(e+?v0zG<_Hhj(n+y`By$}s`yKwNx{M_Yl zT<=R2Na>PL-q-4F5W!-P)$SvmQJ9)C;&M{1-qWYCet9o7p5NQ0bI)jJSYAxTu{ng$ z1zNEE#84shgi24K0>h0)A`n##y7V!Q6dv;3%`Ol`0S5^F@#id{`J%8%DKlfU25|?ZxVDzE1 zOU4rq?f|rio8zq;Z_i)9)O~D>te}zZO{0&39{=h`0Y-s*aC(e&QoQbgpgxq|z6A-_ z6PO;PEEG<8npHvuM?n?;C5lWexU0h}f|Gsor>J-ug%|JK44t&CpT-6Y&zps@T)xC| z0^1KLPQu}k?~^B=Fkegf3H zJH#~~WZ!dt_Fq2IYXg~7a8yc_yofVdL0m0P;>GfnN|0-F6@!6-M2t+~jo3{#>I8|J v(1NFn8=Y^V;bji)8JmLK_i=DW@Xs_&GC-e6*-%ny>W9W&1Bk7XwOjuMHls7? literal 0 HcmV?d00001 diff --git a/cookiecloud_client/README.md b/cookiecloud_client/README.md new file mode 100644 index 0000000..896ad85 --- /dev/null +++ b/cookiecloud_client/README.md @@ -0,0 +1,248 @@ +# CookieCloud Client + +一个独立的、可复用的CookieCloud服务器客户端Python模块。 + +## 特性 + +- ✅ **完全独立** - 无外部依赖,仅使用Python标准库 +- ✅ **类型安全** - 完整的类型提示支持 +- ✅ **异常处理** - 完善的异常体系 +- ✅ **易于使用** - 简洁的API设计 +- ✅ **可扩展** - 支持自定义配置 +- ✅ **文档完善** - 详细的使用文档和示例 + +## 安装 + +将 `cookiecloud_client` 目录复制到您的项目中即可使用。 + +## 快速开始 + +### 基本使用 + +```python +from cookiecloud_client import CookieCloudClient, CookieConfig + +# 创建配置 +config = CookieConfig( + server="https://cookiecloud.example.com", + username="your_username", + password="your_password" +) + +# 创建客户端 +client = CookieCloudClient(config) + +# 下载所有cookies +result = client.download() + +if result.success: + print(f"下载成功!") + print(f"总域名数: {result.total_domains}") + print(f"总Cookie数: {result.total_cookies}") + print(f"下载耗时: {result.download_time:.2f}秒") + + # 获取指定域名的cookie + cookie_str = result.get_cookie_string("example.com") + print(f"example.com的cookie: {cookie_str}") +else: + print(f"下载失败: {result.error_message}") +``` + +### 下载指定域名的Cookie + +```python +# 下载单个域名 +cookie_str = client.download_for_domain("baidu.com") +if cookie_str: + print(f"baidu.com的cookie: {cookie_str}") + +# 批量下载多个域名 +domains = ["baidu.com", "google.com", "github.com"] +cookies = client.download_for_domains(domains) +for domain, cookie in cookies.items(): + print(f"{domain}: {cookie}") +``` + +### 测试连接 + +```python +success, message = client.test_connection() +if success: + print("连接成功!") +else: + print(f"连接失败: {message}") +``` + +## 高级用法 + +### 自定义配置 + +```python +config = CookieConfig( + server="https://cookiecloud.example.com", + username="your_username", + password="your_password", + timeout=60, # 超时时间(秒) + verify_ssl=False, # 是否验证SSL证书 + ignore_cookies=[ # 忽略的cookie名称 + "CookieAutoDeleteBrowsingDataCleanup", + "CookieAutoDeleteCleaningDiscarded" + ] +) +``` + +### 异常处理 + +```python +from cookiecloud_client import ( + CookieCloudClient, + CookieConfig, + CookieCloudError, + ConnectionError, + AuthenticationError, + DataParseError, + NetworkError +) + +try: + client = CookieCloudClient(config) + result = client.download() + +except AuthenticationError as e: + print(f"认证失败: {e.message}") + print(f"用户名: {e.details.get('username')}") + +except ConnectionError as e: + print(f"连接失败: {e.message}") + print(f"服务器: {e.details.get('server')}") + print(f"状态码: {e.details.get('status_code')}") + +except DataParseError as e: + print(f"数据解析失败: {e.message}") + +except NetworkError as e: + print(f"网络错误: {e.message}") + print(f"原始错误: {e.details.get('original_error')}") + +except CookieCloudError as e: + print(f"CookieCloud错误: {e.message}") +``` + +### 使用Cookie数据对象 + +```python +result = client.download() + +# 获取所有域名 +domains = result.get_domains() +print(f"所有域名: {domains}") + +# 遍历所有cookie +for domain, collection in result.cookies.items(): + print(f"\n域名: {domain}") + for cookie in collection.cookies: + print(f" - {cookie.name}={cookie.value[:20]}...") + print(f" 路径: {cookie.path}") + print(f" 安全: {cookie.secure}") + print(f" HttpOnly: {cookie.http_only}") +``` + +## API文档 + +### CookieConfig + +配置类,用于存储CookieCloud服务器配置。 + +**参数:** +- `server` (str): CookieCloud服务器地址 +- `username` (str): 用户名 +- `password` (str): 密码 +- `timeout` (int): 请求超时时间(秒),默认30 +- `verify_ssl` (bool): 是否验证SSL证书,默认True +- `ignore_cookies` (List[str]): 忽略的cookie名称列表 + +### CookieCloudClient + +客户端类,用于与CookieCloud服务器交互。 + +**方法:** + +#### `download() -> DownloadResult` +下载所有cookies + +**返回:** DownloadResult对象 + +#### `download_for_domain(domain: str) -> Optional[str]` +下载指定域名的cookie字符串 + +**参数:** +- `domain` (str): 目标域名 + +**返回:** cookie字符串或None + +#### `download_for_domains(domains: List[str]) -> Dict[str, Optional[str]]` +批量下载多个域名的cookie字符串 + +**参数:** +- `domains` (List[str]): 目标域名列表 + +**返回:** {domain: cookie_string}字典 + +#### `test_connection() -> Tuple[bool, str]` +测试与CookieCloud服务器的连接 + +**返回:** (是否成功, 消息)元组 + +### DownloadResult + +下载结果类。 + +**属性:** +- `success` (bool): 是否成功 +- `cookies` (Dict[str, CookieCollection]): cookie集合字典 +- `error_message` (str): 错误信息 +- `total_domains` (int): 总域名数 +- `total_cookies` (int): 总cookie数 +- `download_time` (float): 下载耗时(秒) + +**方法:** +- `get_cookie_string(domain: str) -> Optional[str]`: 获取指定域名的cookie字符串 +- `get_domains() -> List[str]`: 获取所有域名列表 + +## 异常类 + +### CookieCloudError +基础异常类,所有其他异常都继承自此类。 + +### ConfigurationError +配置错误异常 + +### ConnectionError +连接错误异常 + +### AuthenticationError +认证错误异常 + +### DataParseError +数据解析错误异常 + +### NetworkError +网络错误异常 + +## 依赖 + +- Python 3.7+ +- 仅使用Python标准库 + +## 许可证 + +MIT License + +## 更新日志 + +### v1.0.0 (2026-03-02) +- 初始版本发布 +- 完整的CookieCloud客户端功能 +- 独立模块,无外部依赖 +- 完善的异常处理 +- 详细的文档和示例 diff --git a/cookiecloud_client/__init__.py b/cookiecloud_client/__init__.py new file mode 100644 index 0000000..9342f33 --- /dev/null +++ b/cookiecloud_client/__init__.py @@ -0,0 +1,33 @@ +""" +CookieCloud客户端模块 +一个独立的、可复用的CookieCloud服务器客户端 + +作者: CookieManager Team +版本: 1.0.0 +许可证: MIT +""" + +from .client import CookieCloudClient +from .exceptions import ( + CookieCloudError, + ConnectionError, + AuthenticationError, + DataParseError, + ConfigurationError, + NetworkError +) +from .models import CookieData, CookieConfig +from .version import __version__ + +__all__ = [ + 'CookieCloudClient', + 'CookieCloudError', + 'ConnectionError', + 'AuthenticationError', + 'DataParseError', + 'ConfigurationError', + 'NetworkError', + 'CookieData', + 'CookieConfig', + '__version__' +] diff --git a/cookiecloud_client/__pycache__/__init__.cpython-310.pyc b/cookiecloud_client/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf6e0e0d0900e8d3af35ae5fee3a19e46dcd9df6 GIT binary patch literal 727 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 OL?K_W3)zBwGV?FZpg#xz literal 0 HcmV?d00001 diff --git a/cookiecloud_client/__pycache__/models.cpython-310.pyc b/cookiecloud_client/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..357a5c29df04f59848b5ed9ad352112ea3eafab2 GIT binary patch literal 4450 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 literal 0 HcmV?d00001 diff --git a/cookiecloud_client/__pycache__/tests.cpython-310.pyc b/cookiecloud_client/__pycache__/tests.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a001877fe6b708e83eb92836105c0c7c3a838bb9 GIT binary patch literal 9147 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 literal 0 HcmV?d00001 diff --git a/cookiecloud_client/__pycache__/version.cpython-310.pyc b/cookiecloud_client/__pycache__/version.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa6c6798f746f97540a936463c0672c1d6e4efc6 GIT binary patch literal 306 zcmd1j<>g`kg6M>$nJqy2F^Gc<7=auIATE{w5-AK(3@MCJj44b}OexG!%qc8UEGewP z44Q1Ed_ZZg=batT=B#>> config = CookieConfig( + ... server="https://cookiecloud.example.com", + ... username="your_username", + ... password="your_password" + ... ) + >>> client = CookieCloudClient(config) + >>> result = client.download() + >>> if result.success: + ... print(f"下载成功,共{result.total_domains}个域名") + ... cookie_str = result.get_cookie_string("example.com") + """ + + def __init__(self, config: CookieConfig): + """ + 初始化客户端 + + Args: + config: CookieCloud配置对象 + + Raises: + ConfigurationError: 配置验证失败 + """ + if not isinstance(config, CookieConfig): + raise ConfigurationError("配置参数必须是CookieConfig类型") + + self.config = config + self._last_download_time = None + self._download_count = 0 + + def download(self) -> DownloadResult: + """ + 从CookieCloud服务器下载所有cookies + + Returns: + DownloadResult: 下载结果对象 + + Raises: + ConnectionError: 连接服务器失败 + AuthenticationError: 认证失败 + DataParseError: 数据解析失败 + NetworkError: 网络错误 + """ + start_time = time.time() + + try: + raw_data = self._fetch_data() + cookies = self._parse_cookies(raw_data) + + download_time = time.time() - start_time + self._last_download_time = time.time() + self._download_count += 1 + + total_cookies = sum(len(c.cookies) for c in cookies.values()) + + return DownloadResult( + success=True, + cookies=cookies, + total_domains=len(cookies), + total_cookies=total_cookies, + download_time=download_time + ) + + except CookieCloudError: + raise + except Exception as e: + raise CookieCloudError(f"下载cookies失败: {str(e)}") + + def download_for_domain(self, domain: str) -> Optional[str]: + """ + 下载指定域名的cookie字符串 + + Args: + domain: 目标域名 + + Returns: + Optional[str]: cookie字符串,如果不存在则返回None + + Raises: + ConnectionError: 连接服务器失败 + AuthenticationError: 认证失败 + DataParseError: 数据解析失败 + """ + result = self.download() + return result.get_cookie_string(domain) + + def download_for_domains(self, domains: List[str]) -> Dict[str, Optional[str]]: + """ + 批量下载多个域名的cookie字符串 + + Args: + domains: 目标域名列表 + + Returns: + Dict[str, Optional[str]]: {domain: cookie_string} + """ + result = self.download() + return {domain: result.get_cookie_string(domain) for domain in domains} + + def test_connection(self) -> Tuple[bool, str]: + """ + 测试与CookieCloud服务器的连接 + + Returns: + Tuple[bool, str]: (是否成功, 消息) + """ + try: + self._fetch_data() + return True, "连接成功" + except AuthenticationError as e: + return False, f"认证失败: {e.message}" + except ConnectionError as e: + return False, f"连接失败: {e.message}" + except Exception as e: + return False, f"测试失败: {str(e)}" + + def _fetch_data(self) -> Dict: + """ + 从服务器获取原始数据 + + Returns: + Dict: 原始JSON数据 + + Raises: + ConnectionError: 连接失败 + AuthenticationError: 认证失败 + NetworkError: 网络错误 + """ + url = f"{self.config.server}/get/{self.config.username}" + + try: + data = json.dumps({"password": self.config.password}).encode('utf-8') + + request = urllib.request.Request( + url, + data=data, + headers={ + 'Content-Type': 'application/json', + 'User-Agent': 'CookieCloudClient/1.0' + }, + method='POST' + ) + + response = urllib.request.urlopen( + request, + timeout=self.config.timeout + ) + + if response.status != 200: + if response.status == 401: + raise AuthenticationError( + "认证失败,请检查用户名和密码", + username=self.config.username + ) + elif response.status == 404: + raise ConnectionError( + "用户不存在,请检查用户名", + server=self.config.server, + status_code=response.status + ) + else: + raise ConnectionError( + f"服务器返回错误状态码: {response.status}", + server=self.config.server, + status_code=response.status + ) + + result = json.loads(response.read().decode('utf-8')) + + if not result: + raise DataParseError("服务器返回数据为空") + + return result + + except urllib.error.HTTPError as e: + if e.code == 401: + raise AuthenticationError( + "认证失败,请检查用户名和密码", + username=self.config.username + ) + else: + raise ConnectionError( + f"HTTP错误: {e.code} {e.reason}", + server=self.config.server, + status_code=e.code + ) + except urllib.error.URLError as e: + raise NetworkError( + f"网络连接失败: {e.reason}", + original_error=e + ) + except json.JSONDecodeError as e: + raise DataParseError( + f"JSON解析失败: {str(e)}" + ) + except Exception as e: + if isinstance(e, CookieCloudError): + raise + raise NetworkError( + f"请求失败: {str(e)}", + original_error=e + ) + + def _parse_cookies(self, raw_data: Dict) -> Dict[str, CookieCollection]: + """ + 解析原始cookie数据 + + Args: + raw_data: 原始JSON数据 + + Returns: + Dict[str, CookieCollection]: {domain: CookieCollection} + """ + if raw_data.get("cookie_data"): + contents = raw_data.get("cookie_data") + else: + contents = raw_data + + domain_groups = self._group_by_domain(contents) + + cookies = {} + for domain, cookie_list in domain_groups.items(): + if not cookie_list: + continue + + if self._is_cloudflare_only(cookie_list): + continue + + collection = CookieCollection(domain=domain) + + for cookie_data in cookie_list: + cookie = CookieData( + domain=cookie_data.get('domain', ''), + name=cookie_data.get('name', ''), + value=cookie_data.get('value', ''), + path=cookie_data.get('path', '/'), + secure=cookie_data.get('secure', False), + http_only=cookie_data.get('httpOnly', False) + ) + collection.add_cookie(cookie) + + cookies[domain] = collection + + return cookies + + def _group_by_domain(self, contents: Dict) -> Dict[str, List[Dict]]: + """ + 按域名分组cookies + + Args: + contents: 原始cookie内容 + + Returns: + Dict[str, List[Dict]]: {domain: [cookie_data]} + """ + domain_groups = {} + + for site, cookies in contents.items(): + for cookie in cookies: + domain = cookie.get("domain", "") + if not domain: + continue + + domain_key = self._extract_domain(domain) + if not domain_key: + continue + + if domain_key not in domain_groups: + domain_groups[domain_key] = [] + + domain_groups[domain_key].append(cookie) + + return domain_groups + + def _extract_domain(self, domain: str) -> Optional[str]: + """ + 提取主域名 + + Args: + domain: 原始域名 + + Returns: + Optional[str]: 主域名 + """ + if not domain: + return None + + domain = domain.lstrip('.') + + parts = domain.split('.') + if len(parts) < 2: + return domain + + if len(parts) == 2: + return domain + + return '.'.join(parts[-2:]) + + def _is_cloudflare_only(self, cookie_list: List[Dict]) -> bool: + """ + 检查是否仅包含Cloudflare验证cookie + + Args: + cookie_list: cookie列表 + + Returns: + bool: 是否仅包含cf_clearance + """ + for cookie in cookie_list: + if cookie.get("name") != "cf_clearance": + return False + return True + + @property + def last_download_time(self) -> Optional[float]: + """获取最后下载时间""" + return self._last_download_time + + @property + def download_count(self) -> int: + """获取下载次数""" + return self._download_count diff --git a/cookiecloud_client/examples.py b/cookiecloud_client/examples.py new file mode 100644 index 0000000..9240475 --- /dev/null +++ b/cookiecloud_client/examples.py @@ -0,0 +1,259 @@ +""" +CookieCloud客户端使用示例 +演示如何使用cookiecloud_client模块 +""" +import sys +import os + +# 添加父目录到路径 +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# 示例1: 基本使用 +def example_basic_usage(): + """基本使用示例""" + from cookiecloud_client import CookieCloudClient, CookieConfig + + print("=" * 60) + print("示例1: 基本使用") + print("=" * 60) + + # 创建配置 + config = CookieConfig( + server="https://movie-pilot.org/cookiecloud", + username="hu6n2vcUmzpu7mqUN2rVCg", + password="jtDM5dV9AyqXkZdQVeA9f6" + ) + + # 创建客户端 + client = CookieCloudClient(config) + + # 测试连接 + success, message = client.test_connection() + print(f"连接测试: {message}") + + if success: + # 下载所有cookies + result = client.download() + + if result.success: + print(f"\n✓ 下载成功!") + print(f" 总域名数: {result.total_domains}") + print(f" 总Cookie数: {result.total_cookies}") + print(f" 下载耗时: {result.download_time:.2f}秒") + + # 显示前5个域名 + domains = result.get_domains()[:5] + print(f"\n前5个域名:") + for idx, domain in enumerate(domains, 1): + cookie_str = result.get_cookie_string(domain) + preview = cookie_str[:50] + "..." if len(cookie_str) > 50 else cookie_str + print(f" {idx}. {domain}: {preview}") + else: + print(f"\n✗ 下载失败: {result.error_message}") + + +# 示例2: 下载指定域名的Cookie +def example_download_specific_domain(): + """下载指定域名示例""" + from cookiecloud_client import CookieCloudClient, CookieConfig + + print("\n" + "=" * 60) + print("示例2: 下载指定域名的Cookie") + print("=" * 60) + + config = CookieConfig( + server="https://movie-pilot.org/cookiecloud", + username="hu6n2vcUmzpu7mqUN2rVCg", + password="jtDM5dV9AyqXkZdQVeA9f6" + ) + + client = CookieCloudClient(config) + + # 下载单个域名 + domain = "baidu.com" + cookie_str = client.download_for_domain(domain) + + if cookie_str: + print(f"✓ {domain}的Cookie:") + print(f" {cookie_str[:100]}...") + else: + print(f"✗ 未找到{domain}的Cookie") + + +# 示例3: 批量下载多个域名 +def example_download_multiple_domains(): + """批量下载示例""" + from cookiecloud_client import CookieCloudClient, CookieConfig + + print("\n" + "=" * 60) + print("示例3: 批量下载多个域名") + print("=" * 60) + + config = CookieConfig( + server="https://movie-pilot.org/cookiecloud", + username="hu6n2vcUmzpu7mqUN2rVCg", + password="jtDM5dV9AyqXkZdQVeA9f6" + ) + + client = CookieCloudClient(config) + + # 批量下载 + domains = ["baidu.com", "github.com", "google.com", "bing.com"] + cookies = client.download_for_domains(domains) + + print("批量下载结果:") + for domain, cookie_str in cookies.items(): + if cookie_str: + preview = cookie_str[:50] + "..." + print(f" ✓ {domain}: {preview}") + else: + print(f" ✗ {domain}: 未找到Cookie") + + +# 示例4: 异常处理 +def example_error_handling(): + """异常处理示例""" + from cookiecloud_client import ( + CookieCloudClient, + CookieConfig, + CookieCloudError, + ConnectionError, + AuthenticationError, + NetworkError + ) + + print("\n" + "=" * 60) + print("示例4: 异常处理") + print("=" * 60) + + try: + # 使用错误的凭据 + config = CookieConfig( + server="https://movie-pilot.org/cookiecloud", + username="wrong_user", + password="wrong_pass" + ) + + client = CookieCloudClient(config) + result = client.download() + + except AuthenticationError as e: + print(f"✗ 认证失败: {e.message}") + print(f" 用户名: {e.details.get('username')}") + + except ConnectionError as e: + print(f"✗ 连接失败: {e.message}") + print(f" 服务器: {e.details.get('server')}") + print(f" 状态码: {e.details.get('status_code')}") + + except NetworkError as e: + print(f"✗ 网络错误: {e.message}") + print(f" 原始错误: {e.details.get('original_error')}") + + except CookieCloudError as e: + print(f"✗ CookieCloud错误: {e.message}") + + +# 示例5: 自定义配置 +def example_custom_config(): + """自定义配置示例""" + from cookiecloud_client import CookieCloudClient, CookieConfig + + print("\n" + "=" * 60) + print("示例5: 自定义配置") + print("=" * 60) + + # 自定义配置 + config = CookieConfig( + server="https://movie-pilot.org/cookiecloud", + username="hu6n2vcUmzpu7mqUN2rVCg", + password="jtDM5dV9AyqXkZdQVeA9f6", + timeout=60, # 60秒超时 + verify_ssl=False, # 不验证SSL证书 + ignore_cookies=[ # 忽略的cookie + "CookieAutoDeleteBrowsingDataCleanup", + "CookieAutoDeleteCleaningDiscarded", + "_ga", # 忽略Google Analytics + ] + ) + + client = CookieCloudClient(config) + + print(f"配置信息:") + print(f" 服务器: {config.server}") + print(f" 用户名: {config.username}") + print(f" 超时时间: {config.timeout}秒") + print(f" 验证SSL: {config.verify_ssl}") + print(f" 忽略Cookie: {len(config.ignore_cookies)}个") + + result = client.download() + + if result.success: + print(f"\n✓ 下载成功") + print(f" 总域名数: {result.total_domains}") + print(f" 总Cookie数: {result.total_cookies}") + + +# 示例6: 使用Cookie数据对象 +def example_cookie_data_objects(): + """使用Cookie数据对象示例""" + from cookiecloud_client import CookieCloudClient, CookieConfig + + print("\n" + "=" * 60) + print("示例6: 使用Cookie数据对象") + print("=" * 60) + + config = CookieConfig( + server="https://movie-pilot.org/cookiecloud", + username="hu6n2vcUmzpu7mqUN2rVCg", + password="jtDM5dV9AyqXkZdQVeA9f6" + ) + + client = CookieCloudClient(config) + result = client.download() + + if result.success: + # 获取第一个域名的详细信息 + first_domain = result.get_domains()[0] + collection = result.cookies.get(first_domain) + + if collection: + print(f"域名: {collection.domain}") + print(f"Cookie数量: {len(collection.cookies)}") + print(f"\nCookie详情:") + + for idx, cookie in enumerate(collection.cookies[:3], 1): + print(f" {idx}. {cookie.name}") + print(f" 值: {cookie.value[:30]}...") + print(f" 路径: {cookie.path}") + print(f" 安全: {cookie.secure}") + print(f" HttpOnly: {cookie.http_only}") + print() + + +# 主函数 +def main(): + """运行所有示例""" + print("\n") + print("╔" + "=" * 58 + "╗") + print("║" + " " * 15 + "CookieCloud客户端使用示例" + " " * 17 + "║") + print("╚" + "=" * 58 + "╝") + + try: + example_basic_usage() + example_download_specific_domain() + example_download_multiple_domains() + example_error_handling() + example_custom_config() + example_cookie_data_objects() + + print("\n" + "=" * 60) + print("所有示例执行完成!") + print("=" * 60) + + except Exception as e: + print(f"\n示例执行出错: {str(e)}") + + +if __name__ == "__main__": + main() diff --git a/cookiecloud_client/exceptions.py b/cookiecloud_client/exceptions.py new file mode 100644 index 0000000..5204dbd --- /dev/null +++ b/cookiecloud_client/exceptions.py @@ -0,0 +1,65 @@ +""" +CookieCloud客户端异常类 +""" + + +class CookieCloudError(Exception): + """CookieCloud基础异常类""" + + def __init__(self, message: str, details: dict = None): + self.message = message + self.details = details or {} + super().__init__(self.message) + + def __str__(self): + if self.details: + return f"{self.message} - 详情: {self.details}" + return self.message + + +class ConfigurationError(CookieCloudError): + """配置错误异常""" + + def __init__(self, message: str, config_key: str = None): + details = {'config_key': config_key} if config_key else {} + super().__init__(message, details) + + +class ConnectionError(CookieCloudError): + """连接错误异常""" + + def __init__(self, message: str, server: str = None, status_code: int = None): + details = {} + if server: + details['server'] = server + if status_code: + details['status_code'] = status_code + super().__init__(message, details) + + +class AuthenticationError(CookieCloudError): + """认证错误异常""" + + def __init__(self, message: str, username: str = None): + details = {'username': username} if username else {} + super().__init__(message, details) + + +class DataParseError(CookieCloudError): + """数据解析错误异常""" + + def __init__(self, message: str, raw_data: str = None): + details = {} + if raw_data: + details['raw_data_length'] = len(raw_data) + super().__init__(message, details) + + +class NetworkError(CookieCloudError): + """网络错误异常""" + + def __init__(self, message: str, original_error: Exception = None): + details = {} + if original_error: + details['original_error'] = str(original_error) + super().__init__(message, details) diff --git a/cookiecloud_client/models.py b/cookiecloud_client/models.py new file mode 100644 index 0000000..c0a877b --- /dev/null +++ b/cookiecloud_client/models.py @@ -0,0 +1,126 @@ +""" +CookieCloud数据模型 +""" +from dataclasses import dataclass, field +from typing import Optional, Dict, List +from datetime import datetime + + +@dataclass +class CookieConfig: + """CookieCloud配置""" + server: str + username: str + password: str + timeout: int = 30 + verify_ssl: bool = True + ignore_cookies: List[str] = field(default_factory=lambda: [ + "CookieAutoDeleteBrowsingDataCleanup", + "CookieAutoDeleteCleaningDiscarded" + ]) + + def __post_init__(self): + """验证配置参数""" + if not self.server: + raise ValueError("服务器地址不能为空") + if not self.username: + raise ValueError("用户名不能为空") + if not self.password: + raise ValueError("密码不能为空") + if not self.server.startswith(('http://', 'https://')): + self.server = f"https://{self.server}" + + +@dataclass +class CookieData: + """Cookie数据""" + domain: str + name: str + value: str + path: str = "/" + secure: bool = False + http_only: bool = False + expiry: Optional[datetime] = None + + def to_dict(self) -> Dict: + """转换为字典""" + return { + 'domain': self.domain, + 'name': self.name, + 'value': self.value, + 'path': self.path, + 'secure': self.secure, + 'httpOnly': self.http_only, + 'expiry': self.expiry.isoformat() if self.expiry else None + } + + @classmethod + def from_dict(cls, data: Dict) -> 'CookieData': + """从字典创建""" + expiry = None + if data.get('expiry'): + try: + expiry = datetime.fromisoformat(data['expiry']) + except (ValueError, TypeError): + pass + + return cls( + domain=data.get('domain', ''), + name=data.get('name', ''), + value=data.get('value', ''), + path=data.get('path', '/'), + secure=data.get('secure', False), + http_only=data.get('httpOnly', False), + expiry=expiry + ) + + +@dataclass +class CookieCollection: + """Cookie集合""" + domain: str + cookies: List[CookieData] = field(default_factory=list) + + def to_cookie_string(self, ignore_list: List[str] = None) -> str: + """转换为cookie字符串""" + ignore_list = ignore_list or [] + cookie_parts = [] + + for cookie in self.cookies: + if cookie.name not in ignore_list: + cookie_parts.append(f"{cookie.name}={cookie.value}") + + return ";".join(cookie_parts) + + def add_cookie(self, cookie: CookieData): + """添加cookie""" + self.cookies.append(cookie) + + def get_cookie_by_name(self, name: str) -> Optional[CookieData]: + """根据名称获取cookie""" + for cookie in self.cookies: + if cookie.name == name: + return cookie + return None + + +@dataclass +class DownloadResult: + """下载结果""" + success: bool + cookies: Dict[str, CookieCollection] = field(default_factory=dict) + error_message: str = "" + total_domains: int = 0 + total_cookies: int = 0 + download_time: float = 0.0 + + def get_cookie_string(self, domain: str) -> Optional[str]: + """获取指定域名的cookie字符串""" + collection = self.cookies.get(domain) + if collection: + return collection.to_cookie_string() + return None + + def get_domains(self) -> List[str]: + """获取所有域名列表""" + return list(self.cookies.keys()) diff --git a/cookiecloud_client/requirements.txt b/cookiecloud_client/requirements.txt new file mode 100644 index 0000000..a1561de --- /dev/null +++ b/cookiecloud_client/requirements.txt @@ -0,0 +1,14 @@ +# CookieCloud客户端依赖项 +# 此模块完全独立,仅使用Python标准库 + +# Python版本要求 +Python>=3.7 + +# 无外部依赖 +# 仅使用Python标准库模块: +# - json +# - urllib +# - dataclasses +# - typing +# - datetime +# - unittest (用于测试) diff --git a/cookiecloud_client/setup.py b/cookiecloud_client/setup.py new file mode 100644 index 0000000..f95f740 --- /dev/null +++ b/cookiecloud_client/setup.py @@ -0,0 +1,54 @@ +""" +CookieCloud客户端安装脚本 +""" +from setuptools import setup, find_packages + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="cookiecloud-client", + version="1.0.0", + author="CookieManager Team", + author_email="support@example.com", + description="一个独立的、可复用的CookieCloud服务器客户端", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/example/cookiecloud-client", + packages=find_packages(), + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + ], + python_requires=">=3.7", + install_requires=[ + # 无外部依赖,仅使用Python标准库 + ], + extras_require={ + "dev": [ + "pytest>=6.0", + "pytest-cov>=2.0", + "black>=21.0", + "flake8>=3.9", + ], + }, + entry_points={ + "console_scripts": [ + "cookiecloud-client=cookiecloud_client.cli:main", + ], + }, + project_urls={ + "Bug Reports": "https://github.com/example/cookiecloud-client/issues", + "Source": "https://github.com/example/cookiecloud-client", + "Documentation": "https://github.com/example/cookiecloud-client#readme", + }, +) diff --git a/cookiecloud_client/tests.py b/cookiecloud_client/tests.py new file mode 100644 index 0000000..6612326 --- /dev/null +++ b/cookiecloud_client/tests.py @@ -0,0 +1,295 @@ +""" +CookieCloud客户端单元测试 +""" +import unittest +from unittest.mock import Mock, patch, MagicMock +import json +from cookiecloud_client import ( + CookieCloudClient, + CookieConfig, + CookieCloudError, + ConfigurationError, + ConnectionError, + AuthenticationError, + DataParseError, + NetworkError +) + + +class TestCookieConfig(unittest.TestCase): + """测试CookieConfig配置类""" + + def test_valid_config(self): + """测试有效配置""" + config = CookieConfig( + server="https://example.com", + username="user", + password="pass" + ) + self.assertEqual(config.server, "https://example.com") + self.assertEqual(config.username, "user") + self.assertEqual(config.password, "pass") + self.assertEqual(config.timeout, 30) + self.assertTrue(config.verify_ssl) + + def test_config_with_custom_params(self): + """测试自定义参数配置""" + config = CookieConfig( + server="https://example.com", + username="user", + password="pass", + timeout=60, + verify_ssl=False, + ignore_cookies=["test_cookie"] + ) + self.assertEqual(config.timeout, 60) + self.assertFalse(config.verify_ssl) + self.assertEqual(config.ignore_cookies, ["test_cookie"]) + + def test_config_auto_add_protocol(self): + """测试自动添加协议""" + config = CookieConfig( + server="example.com", + username="user", + password="pass" + ) + self.assertTrue(config.server.startswith("https://")) + + def test_config_empty_server(self): + """测试空服务器地址""" + with self.assertRaises(ValueError): + CookieConfig(server="", username="user", password="pass") + + def test_config_empty_username(self): + """测试空用户名""" + with self.assertRaises(ValueError): + CookieConfig(server="https://example.com", username="", password="pass") + + def test_config_empty_password(self): + """测试空密码""" + with self.assertRaises(ValueError): + CookieConfig(server="https://example.com", username="user", password="") + + +class TestCookieCloudClient(unittest.TestCase): + """测试CookieCloudClient客户端类""" + + def setUp(self): + """测试前准备""" + self.config = CookieConfig( + server="https://test.example.com", + username="testuser", + password="testpass" + ) + self.client = CookieCloudClient(self.config) + + def test_client_initialization(self): + """测试客户端初始化""" + self.assertIsInstance(self.client.config, CookieConfig) + self.assertIsNone(self.client.last_download_time) + self.assertEqual(self.client.download_count, 0) + + def test_client_invalid_config(self): + """测试无效配置""" + with self.assertRaises(ConfigurationError): + CookieCloudClient("invalid_config") + + @patch('urllib.request.urlopen') + def test_download_success(self, mock_urlopen): + """测试成功下载""" + mock_response = Mock() + mock_response.status = 200 + mock_response.read.return_value = json.dumps({ + "cookie_data": { + "test.com": [ + { + "domain": "test.com", + "name": "session", + "value": "test_value", + "path": "/", + "secure": False, + "httpOnly": False + } + ] + } + }).encode('utf-8') + mock_urlopen.return_value = mock_response + + result = self.client.download() + + self.assertTrue(result.success) + self.assertEqual(result.total_domains, 1) + self.assertEqual(result.total_cookies, 1) + self.assertIsNotNone(self.client.last_download_time) + self.assertEqual(self.client.download_count, 1) + + @patch('urllib.request.urlopen') + def test_download_authentication_error(self, mock_urlopen): + """测试认证失败""" + import urllib.error + + mock_urlopen.side_effect = urllib.error.HTTPError( + url="https://test.example.com/get/testuser", + code=401, + msg="Unauthorized", + hdrs={}, + fp=None + ) + + with self.assertRaises(AuthenticationError): + self.client.download() + + @patch('urllib.request.urlopen') + def test_download_connection_error(self, mock_urlopen): + """测试连接错误""" + import urllib.error + + mock_urlopen.side_effect = urllib.error.HTTPError( + url="https://test.example.com/get/testuser", + code=404, + msg="Not Found", + hdrs={}, + fp=None + ) + + with self.assertRaises(ConnectionError): + self.client.download() + + @patch('urllib.request.urlopen') + def test_download_network_error(self, mock_urlopen): + """测试网络错误""" + import urllib.error + + mock_urlopen.side_effect = urllib.error.URLError("Connection refused") + + with self.assertRaises(NetworkError): + self.client.download() + + @patch('urllib.request.urlopen') + def test_download_empty_data(self, mock_urlopen): + """测试空数据""" + mock_response = Mock() + mock_response.status = 200 + mock_response.read.return_value = json.dumps({}).encode('utf-8') + mock_urlopen.return_value = mock_response + + with self.assertRaises(DataParseError): + self.client.download() + + @patch('urllib.request.urlopen') + def test_test_connection_success(self, mock_urlopen): + """测试连接测试成功""" + mock_response = Mock() + mock_response.status = 200 + mock_response.read.return_value = json.dumps({ + "cookie_data": {} + }).encode('utf-8') + mock_urlopen.return_value = mock_response + + success, message = self.client.test_connection() + + self.assertTrue(success) + self.assertEqual(message, "连接成功") + + @patch('urllib.request.urlopen') + def test_test_connection_failure(self, mock_urlopen): + """测试连接测试失败""" + import urllib.error + + mock_urlopen.side_effect = urllib.error.HTTPError( + url="https://test.example.com/get/testuser", + code=401, + msg="Unauthorized", + hdrs={}, + fp=None + ) + + success, message = self.client.test_connection() + + self.assertFalse(success) + self.assertIn("认证失败", message) + + @patch('urllib.request.urlopen') + def test_download_for_domain(self, mock_urlopen): + """测试下载指定域名""" + mock_response = Mock() + mock_response.status = 200 + mock_response.read.return_value = json.dumps({ + "cookie_data": { + "test.com": [ + { + "domain": "test.com", + "name": "session", + "value": "test_value", + "path": "/", + "secure": False, + "httpOnly": False + } + ] + } + }).encode('utf-8') + mock_urlopen.return_value = mock_response + + cookie_str = self.client.download_for_domain("test.com") + + self.assertIsNotNone(cookie_str) + self.assertIn("session=test_value", cookie_str) + + @patch('urllib.request.urlopen') + def test_download_for_domains(self, mock_urlopen): + """测试批量下载多个域名""" + mock_response = Mock() + mock_response.status = 200 + mock_response.read.return_value = json.dumps({ + "cookie_data": { + "test1.com": [ + {"domain": "test1.com", "name": "cookie1", "value": "value1", "path": "/"} + ], + "test2.com": [ + {"domain": "test2.com", "name": "cookie2", "value": "value2", "path": "/"} + ] + } + }).encode('utf-8') + mock_urlopen.return_value = mock_response + + domains = ["test1.com", "test2.com", "test3.com"] + cookies = self.client.download_for_domains(domains) + + self.assertEqual(len(cookies), 3) + self.assertIn("cookie1=value1", cookies["test1.com"]) + self.assertIn("cookie2=value2", cookies["test2.com"]) + self.assertIsNone(cookies["test3.com"]) + + +class TestExceptions(unittest.TestCase): + """测试异常类""" + + def test_cookie_cloud_error(self): + """测试基础异常""" + error = CookieCloudError("测试错误", {"key": "value"}) + self.assertEqual(error.message, "测试错误") + self.assertEqual(error.details, {"key": "value"}) + self.assertIn("测试错误", str(error)) + + def test_configuration_error(self): + """测试配置错误""" + error = ConfigurationError("配置错误", config_key="server") + self.assertEqual(error.message, "配置错误") + self.assertEqual(error.details["config_key"], "server") + + def test_connection_error(self): + """测试连接错误""" + error = ConnectionError("连接失败", server="example.com", status_code=404) + self.assertEqual(error.message, "连接失败") + self.assertEqual(error.details["server"], "example.com") + self.assertEqual(error.details["status_code"], 404) + + def test_authentication_error(self): + """测试认证错误""" + error = AuthenticationError("认证失败", username="testuser") + self.assertEqual(error.message, "认证失败") + self.assertEqual(error.details["username"], "testuser") + + +if __name__ == '__main__': + unittest.main() diff --git a/cookiecloud_client/version.py b/cookiecloud_client/version.py new file mode 100644 index 0000000..a7edd13 --- /dev/null +++ b/cookiecloud_client/version.py @@ -0,0 +1,8 @@ +""" +版本信息 +""" + +__version__ = '1.0.0' +__author__ = 'CookieManager Team' +__email__ = 'support@example.com' +__license__ = 'MIT' diff --git a/log/app.log b/log/app.log index 39bfe99..f1a2cb2 100644 --- a/log/app.log +++ b/log/app.log @@ -343,3 +343,650 @@ 2026-03-02 16:38:22,061 - INFO - 用户 用户A 处理完成 2026-03-02 16:38:22,075 - INFO - ===== Cookie 监控结束 ===== 2026-03-02 16:38:22,102 - INFO - 成功处理: 1 个用户,失败: 0 个用户 +2026-03-02 18:56:27,278 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 18:56:27,279 - INFO - 开始处理用户: 用户A +2026-03-02 18:56:27,279 - INFO - 处理网站: 用户A - 网站A +2026-03-02 18:56:27,280 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 18:56:27,280 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 18:56:54,215 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 18:56:54,216 - INFO - 登录失败: 网站A +2026-03-02 18:56:54,435 - INFO - 已发送失败通知: 用户A - 网站A +2026-03-02 18:56:54,508 - INFO - 已达到最大失败次数,已发送通知并重置计数: 网站A +2026-03-02 18:56:55,613 - INFO - 用户 用户A 处理完成 +2026-03-02 18:56:55,615 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 18:56:55,616 - INFO - 成功处理: 1 个用户,失败: 0 个用户 +2026-03-02 18:57:16,772 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 18:57:16,772 - INFO - 开始处理用户: 用户A +2026-03-02 18:57:16,773 - INFO - 处理网站: 用户A - 网站A +2026-03-02 18:57:16,773 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 18:57:16,774 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 18:57:38,432 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 18:57:38,433 - INFO - 登录失败: 网站A +2026-03-02 18:57:38,434 - INFO - 当前失败次数: 1/3 +2026-03-02 18:57:39,475 - INFO - 用户 用户A 处理完成 +2026-03-02 18:57:39,479 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 18:57:39,480 - INFO - 成功处理: 1 个用户,失败: 0 个用户 +2026-03-02 19:06:12,869 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 19:06:12,870 - INFO - 开始处理用户: 用户A +2026-03-02 19:06:12,871 - INFO - 处理网站: 用户A - 网站A +2026-03-02 19:06:12,871 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:06:12,872 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:06:13,899 - INFO - 获取到 4 个 cookie: +2026-03-02 19:06:13,900 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:06:13,902 - INFO - - edentoken: 13548569666 +2026-03-02 19:06:13,903 - INFO - - user: 13548569666 +2026-03-02 19:06:13,904 - INFO - - userType: 2 +2026-03-02 19:08:41,909 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:08:41,910 - INFO - 登录失败: 网站A +2026-03-02 19:08:41,913 - INFO - 当前失败次数: 2/3 +2026-03-02 19:08:43,011 - INFO - 用户 用户A 处理完成 +2026-03-02 19:08:43,019 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 19:08:43,020 - INFO - 成功处理: 1 个用户,失败: 0 个用户 +2026-03-02 19:09:07,392 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 19:09:07,393 - INFO - 开始处理用户: 用户A +2026-03-02 19:09:07,393 - INFO - 处理网站: 用户A - 网站A +2026-03-02 19:09:07,393 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:09:07,394 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:09:08,548 - INFO - 获取到 4 个 cookie: +2026-03-02 19:09:08,549 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:09:08,549 - INFO - - edentoken: 13548569666 +2026-03-02 19:09:08,549 - INFO - - user: 13548569666 +2026-03-02 19:09:08,550 - INFO - - userType: 2 +2026-03-02 19:09:24,826 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:09:24,827 - INFO - 登录失败: 网站A +2026-03-02 19:09:24,974 - INFO - 已发送失败通知: 用户A - 网站A +2026-03-02 19:09:24,975 - INFO - 已达到最大失败次数,已发送通知并重置计数: 网站A +2026-03-02 19:09:26,021 - INFO - 用户 用户A 处理完成 +2026-03-02 19:09:26,022 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 19:09:26,023 - INFO - 成功处理: 1 个用户,失败: 0 个用户 +2026-03-02 19:18:24,406 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 19:18:24,408 - INFO - 开始处理用户: 用户A +2026-03-02 19:18:24,408 - INFO - 处理网站: 用户A - 网站A +2026-03-02 19:18:24,409 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:18:24,409 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:18:25,149 - INFO - 获取到 4 个 cookie: +2026-03-02 19:18:25,149 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:18:25,150 - INFO - - edentoken: 13548569666 +2026-03-02 19:18:25,150 - INFO - - user: 13548569666 +2026-03-02 19:18:25,151 - INFO - - userType: 2 +2026-03-02 19:19:20,811 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:19:20,812 - INFO - 登录失败: 网站A +2026-03-02 19:19:20,826 - INFO - 当前失败次数: 1/3 +2026-03-02 19:19:21,868 - INFO - 用户 用户A 处理完成 +2026-03-02 19:19:21,875 - INFO - 开始处理用户: 用户B +2026-03-02 19:19:21,876 - INFO - 处理网站: 用户B - 网站A +2026-03-02 19:19:21,879 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:19:21,883 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:19:22,305 - INFO - 获取到 6 个 cookie: +2026-03-02 19:19:22,306 - INFO - - _qimei_uuid42: 1a302130f16100ca18af370a47cb8afce807c4a076 +2026-03-02 19:19:22,306 - INFO - - _qimei_fingerprint: 50aa79f919511142818459f44ac38dee +2026-03-02 19:19:22,307 - INFO - - _qimei_i_3: 64c844d0c35e05dc9796fc355d8470e5a3eba5f0410e00d3e7... +2026-03-02 19:19:22,307 - INFO - - _qimei_h38: f8e0750618af370a47cb8afc0200000c91a302 +2026-03-02 19:19:22,308 - INFO - - merchantType: web +2026-03-02 19:19:22,308 - INFO - - _qimei_i_1: 63f25ad49d0f50dcc497f83153d525e3f0eef5f51508518ae5... +2026-03-02 19:19:42,892 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:19:42,893 - INFO - 登录失败: 网站A +2026-03-02 19:19:42,897 - INFO - 当前失败次数: 1/3 +2026-03-02 19:19:43,952 - INFO - 用户 用户B 处理完成 +2026-03-02 19:19:43,953 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 19:19:43,956 - INFO - 成功处理: 2 个用户,失败: 0 个用户 +2026-03-02 19:28:58,190 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 19:28:58,195 - INFO - 开始处理用户: 用户A +2026-03-02 19:28:58,196 - INFO - 处理网站: 用户A - 网站A +2026-03-02 19:28:58,197 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:28:58,198 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:28:58,934 - INFO - 获取到 4 个 cookie: +2026-03-02 19:28:58,934 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:28:58,935 - INFO - - edentoken: 13548569666 +2026-03-02 19:28:58,936 - INFO - - user: 13548569666 +2026-03-02 19:28:58,936 - INFO - - userType: 2 +2026-03-02 19:29:09,680 - INFO - 开始设置 4 个 cookie +2026-03-02 19:29:10,559 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:29:10,589 - INFO - 设置 cookie: edentoken +2026-03-02 19:29:10,616 - INFO - 设置 cookie: user +2026-03-02 19:29:10,649 - INFO - 设置 cookie: userType +2026-03-02 19:31:57,850 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:31:57,874 - INFO - 登录失败: 网站A +2026-03-02 19:31:57,908 - INFO - 当前失败次数: 2/3 +2026-03-02 19:31:59,017 - INFO - 用户 用户A 处理完成 +2026-03-02 19:31:59,026 - INFO - 开始处理用户: 用户B +2026-03-02 19:31:59,034 - INFO - 处理网站: 用户B - 网站A +2026-03-02 19:31:59,049 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:31:59,057 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:31:59,548 - INFO - 获取到 6 个 cookie: +2026-03-02 19:31:59,549 - INFO - - _qimei_uuid42: 1a302130f16100ca18af370a47cb8afce807c4a076 +2026-03-02 19:31:59,549 - INFO - - _qimei_fingerprint: 50aa79f919511142818459f44ac38dee +2026-03-02 19:31:59,550 - INFO - - _qimei_i_3: 64c844d0c35e05dc9796fc355d8470e5a3eba5f0410e00d3e7... +2026-03-02 19:31:59,551 - INFO - - _qimei_h38: f8e0750618af370a47cb8afc0200000c91a302 +2026-03-02 19:31:59,555 - INFO - - merchantType: web +2026-03-02 19:31:59,557 - INFO - - _qimei_i_1: 63f25ad49d0f50dcc497f83153d525e3f0eef5f51508518ae5... +2026-03-02 19:32:04,579 - INFO - 开始设置 6 个 cookie +2026-03-02 19:32:05,108 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:32:05,123 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:32:05,134 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:32:05,152 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:32:05,176 - INFO - 设置 cookie: merchantType +2026-03-02 19:32:05,191 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:32:20,855 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:32:20,856 - INFO - 登录失败: 网站A +2026-03-02 19:32:20,861 - INFO - 当前失败次数: 2/3 +2026-03-02 19:32:21,948 - INFO - 用户 用户B 处理完成 +2026-03-02 19:32:21,953 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 19:32:21,970 - INFO - 成功处理: 2 个用户,失败: 0 个用户 +2026-03-02 19:41:26,080 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 19:41:26,081 - INFO - 开始处理用户: 用户A +2026-03-02 19:41:26,082 - INFO - 处理网站: 用户A - 网站A +2026-03-02 19:41:26,082 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:41:26,082 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:41:27,131 - INFO - 获取到 4 个 cookie: +2026-03-02 19:41:27,132 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:41:27,134 - INFO - - edentoken: 13548569666 +2026-03-02 19:41:27,135 - INFO - - user: 13548569666 +2026-03-02 19:41:27,137 - INFO - - userType: 2 +2026-03-02 19:41:32,196 - INFO - 开始设置 4 个 cookie +2026-03-02 19:41:32,218 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:41:32,256 - INFO - 设置 cookie: edentoken +2026-03-02 19:41:32,291 - INFO - 设置 cookie: user +2026-03-02 19:41:32,315 - INFO - 设置 cookie: userType +2026-03-02 19:41:47,436 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:41:47,436 - INFO - 登录失败: 网站A +2026-03-02 19:41:47,804 - INFO - 已发送失败通知: 用户A - 网站A +2026-03-02 19:41:47,828 - INFO - 已达到最大失败次数,已发送通知并重置计数: 网站A +2026-03-02 19:41:48,879 - INFO - 用户 用户A 处理完成 +2026-03-02 19:41:48,880 - INFO - 开始处理用户: 用户B +2026-03-02 19:41:48,881 - INFO - 处理网站: 用户B - 网站A +2026-03-02 19:41:48,882 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:41:48,882 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:41:49,422 - INFO - 获取到 17 个 cookie: +2026-03-02 19:41:49,426 - INFO - - _qimei_uuid42: 1a302130f16100ca18af370a47cb8afce807c4a076 +2026-03-02 19:41:49,435 - INFO - - _qimei_fingerprint: 50aa79f919511142818459f44ac38dee +2026-03-02 19:41:49,481 - INFO - - _qimei_i_3: 64c844d0c35e05dc9796fc355d8470e5a3eba5f0410e00d3e7... +2026-03-02 19:41:49,561 - INFO - - _qimei_h38: f8e0750618af370a47cb8afc0200000c91a302 +2026-03-02 19:41:49,583 - INFO - - merchantType: web +2026-03-02 19:41:49,596 - INFO - - _qimei_i_1: 63f25ad49d0f50dcc497f83153d525e3f0eef5f51508518ae5... +2026-03-02 19:41:49,609 - INFO - - JSESSIONID: B93CC37376EFC653A59B550FE761CC72 +2026-03-02 19:41:49,612 - INFO - - _qimei_i_1: 0 +2026-03-02 19:41:49,662 - INFO - - merchantType: 0 +2026-03-02 19:41:49,665 - INFO - - _qimei_h38: 0 +2026-03-02 19:41:49,666 - INFO - - _qimei_i_3: 0 +2026-03-02 19:41:49,668 - INFO - - _qimei_fingerprint: 0 +2026-03-02 19:41:49,672 - INFO - - _qimei_uuid42: 0 +2026-03-02 19:41:49,672 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:41:49,674 - INFO - - edentoken: 13548569666 +2026-03-02 19:41:49,675 - INFO - - user: 13548569666 +2026-03-02 19:41:49,676 - INFO - - userType: 2 +2026-03-02 19:41:53,678 - INFO - 开始设置 17 个 cookie +2026-03-02 19:41:54,168 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:41:54,177 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:41:54,191 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:41:54,205 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:41:54,218 - INFO - 设置 cookie: merchantType +2026-03-02 19:41:54,228 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:41:54,240 - INFO - 设置 cookie: JSESSIONID +2026-03-02 19:41:54,250 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:41:54,263 - INFO - 设置 cookie: merchantType +2026-03-02 19:41:54,277 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:41:54,290 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:41:54,302 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:41:54,315 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:41:54,328 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:41:54,339 - INFO - 设置 cookie: edentoken +2026-03-02 19:41:54,350 - INFO - 设置 cookie: user +2026-03-02 19:41:54,362 - INFO - 设置 cookie: userType +2026-03-02 19:44:10,871 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:44:10,872 - INFO - 登录失败: 网站A +2026-03-02 19:44:11,050 - INFO - 已发送失败通知: 用户B - 网站A +2026-03-02 19:44:11,111 - INFO - 已达到最大失败次数,已发送通知并重置计数: 网站A +2026-03-02 19:44:12,178 - INFO - 用户 用户B 处理完成 +2026-03-02 19:44:12,181 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 19:44:12,187 - INFO - 成功处理: 2 个用户,失败: 0 个用户 +2026-03-02 19:44:29,316 - INFO - ===== Cookie 监控开始 ===== +2026-03-02 19:44:29,316 - INFO - 开始处理用户: 用户A +2026-03-02 19:44:29,317 - INFO - 处理网站: 用户A - 网站A +2026-03-02 19:44:29,317 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:44:29,318 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:44:31,320 - INFO - 获取到 4 个 cookie: +2026-03-02 19:44:31,321 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:44:31,324 - INFO - - edentoken: 13548569666 +2026-03-02 19:44:31,325 - INFO - - user: 13548569666 +2026-03-02 19:44:31,326 - INFO - - userType: 2 +2026-03-02 19:44:34,631 - INFO - 开始设置 4 个 cookie +2026-03-02 19:44:34,649 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:44:34,666 - INFO - 设置 cookie: edentoken +2026-03-02 19:44:34,684 - INFO - 设置 cookie: user +2026-03-02 19:44:34,709 - INFO - 设置 cookie: userType +2026-03-02 19:44:49,463 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:44:49,465 - INFO - 登录失败: 网站A +2026-03-02 19:44:49,466 - INFO - 当前失败次数: 1/3 +2026-03-02 19:44:50,505 - INFO - 用户 用户A 处理完成 +2026-03-02 19:44:50,506 - INFO - 开始处理用户: 用户B +2026-03-02 19:44:50,506 - INFO - 处理网站: 用户B - 网站A +2026-03-02 19:44:50,507 - INFO - 尝试使用本地 cookie 登录: 网站A +2026-03-02 19:44:50,508 - INFO - 从 Cookie Cloud 获取 cookie: 网站A +2026-03-02 19:44:50,863 - INFO - 获取到 17 个 cookie: +2026-03-02 19:44:50,864 - INFO - - _qimei_uuid42: 1a302130f16100ca18af370a47cb8afce807c4a076 +2026-03-02 19:44:50,865 - INFO - - _qimei_fingerprint: 50aa79f919511142818459f44ac38dee +2026-03-02 19:44:50,865 - INFO - - _qimei_i_3: 64c844d0c35e05dc9796fc355d8470e5a3eba5f0410e00d3e7... +2026-03-02 19:44:50,866 - INFO - - _qimei_h38: f8e0750618af370a47cb8afc0200000c91a302 +2026-03-02 19:44:50,866 - INFO - - merchantType: web +2026-03-02 19:44:50,867 - INFO - - _qimei_i_1: 63f25ad49d0f50dcc497f83153d525e3f0eef5f51508518ae5... +2026-03-02 19:44:50,868 - INFO - - JSESSIONID: B93CC37376EFC653A59B550FE761CC72 +2026-03-02 19:44:50,868 - INFO - - _qimei_i_1: 0 +2026-03-02 19:44:50,868 - INFO - - merchantType: 0 +2026-03-02 19:44:50,869 - INFO - - _qimei_h38: 0 +2026-03-02 19:44:50,870 - INFO - - _qimei_i_3: 0 +2026-03-02 19:44:50,870 - INFO - - _qimei_fingerprint: 0 +2026-03-02 19:44:50,871 - INFO - - _qimei_uuid42: 0 +2026-03-02 19:44:50,872 - INFO - - LMKBIUSS: eyJhY2NvdW50SWQiOjIzMzk0OTQ3NTAwMjA0OSwicGhvbmUiOi... +2026-03-02 19:44:50,872 - INFO - - edentoken: 13548569666 +2026-03-02 19:44:50,873 - INFO - - user: 13548569666 +2026-03-02 19:44:50,873 - INFO - - userType: 2 +2026-03-02 19:44:54,212 - INFO - 开始设置 17 个 cookie +2026-03-02 19:44:54,239 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:44:54,258 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:44:54,286 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:44:54,306 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:44:54,323 - INFO - 设置 cookie: merchantType +2026-03-02 19:44:54,344 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:44:54,366 - INFO - 设置 cookie: JSESSIONID +2026-03-02 19:44:54,391 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:44:54,414 - INFO - 设置 cookie: merchantType +2026-03-02 19:44:54,436 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:44:54,456 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:44:54,474 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:44:54,503 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:44:54,529 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:44:54,554 - INFO - 设置 cookie: edentoken +2026-03-02 19:44:54,571 - INFO - 设置 cookie: user +2026-03-02 19:44:54,590 - INFO - 设置 cookie: userType +2026-03-02 19:45:10,024 - INFO - Cookie Cloud 登录失败: 网站A +2026-03-02 19:45:10,025 - INFO - 登录失败: 网站A +2026-03-02 19:45:10,026 - INFO - 当前失败次数: 1/3 +2026-03-02 19:45:11,099 - INFO - 用户 用户B 处理完成 +2026-03-02 19:45:11,100 - INFO - ===== Cookie 监控结束 ===== +2026-03-02 19:45:11,100 - INFO - 成功处理: 2 个用户,失败: 0 个用户 +2026-03-02 19:47:06,309 - INFO - ================================================== +2026-03-02 19:47:06,310 - INFO - Cookie 监控开始 +2026-03-02 19:47:06,310 - INFO - 时间: 2026-03-02 19:47:06 +2026-03-02 19:47:06,311 - INFO - ================================================== +2026-03-02 19:47:06,311 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-02 19:47:06,312 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 19:47:08,398 - INFO - 获取到 278 个域名,共 803 个 cookie +2026-03-02 19:47:08,399 - INFO - +处理网站: 网站A +2026-03-02 19:47:08,399 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 19:47:08,400 - INFO - 找到 4 个 cookie +2026-03-02 19:47:19,291 - INFO - 开始设置 4 个 cookie +2026-03-02 19:47:19,316 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:47:19,336 - INFO - 设置 cookie: edentoken +2026-03-02 19:47:19,351 - INFO - 设置 cookie: user +2026-03-02 19:47:19,367 - INFO - 设置 cookie: userType +2026-03-02 19:49:03,692 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:49:03,700 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-02 19:49:03,703 - INFO - 总网站数: 1 +2026-03-02 19:49:03,704 - INFO - 成功: 0, 失败: 1 +2026-03-02 19:49:03,705 - INFO - +失败详情: +2026-03-02 19:49:03,706 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:49:04,751 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-02 19:49:04,790 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 19:49:05,278 - INFO - 获取到 814 个域名,共 2941 个 cookie +2026-03-02 19:49:05,282 - INFO - +处理网站: 网站A +2026-03-02 19:49:05,299 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 19:49:05,303 - INFO - 找到 17 个 cookie +2026-03-02 19:49:19,363 - INFO - 开始设置 17 个 cookie +2026-03-02 19:49:19,378 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:49:19,392 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:49:19,408 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:49:19,426 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:49:19,443 - INFO - 设置 cookie: merchantType +2026-03-02 19:49:19,459 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:49:19,475 - INFO - 设置 cookie: JSESSIONID +2026-03-02 19:49:19,490 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:49:19,506 - INFO - 设置 cookie: merchantType +2026-03-02 19:49:19,521 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:49:19,534 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:49:19,549 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:49:19,562 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:49:19,578 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:49:19,592 - INFO - 设置 cookie: edentoken +2026-03-02 19:49:19,601 - INFO - 设置 cookie: user +2026-03-02 19:49:19,615 - INFO - 设置 cookie: userType +2026-03-02 19:51:34,896 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:51:34,902 - INFO - +==================== 用户 用户B 处理结果汇总 ==================== +2026-03-02 19:51:34,904 - INFO - 总网站数: 1 +2026-03-02 19:51:34,905 - INFO - 成功: 0, 失败: 1 +2026-03-02 19:51:34,906 - INFO - +失败详情: +2026-03-02 19:51:34,907 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:51:35,961 - INFO - +================================================== +2026-03-02 19:51:35,976 - INFO - Cookie 监控结束 +2026-03-02 19:51:35,988 - INFO - ================================================== +2026-03-02 19:52:33,025 - INFO - ================================================== +2026-03-02 19:52:33,026 - INFO - Cookie 监控开始 +2026-03-02 19:52:33,026 - INFO - 时间: 2026-03-02 19:52:33 +2026-03-02 19:52:33,027 - INFO - ================================================== +2026-03-02 19:52:33,027 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-02 19:52:33,028 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 19:52:35,067 - INFO - 获取到 278 个域名,共 801 个 cookie +2026-03-02 19:52:35,069 - INFO - +处理网站: 网站A +2026-03-02 19:52:35,070 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 19:52:35,071 - INFO - 找到 4 个 cookie +2026-03-02 19:52:37,975 - INFO - 开始设置 4 个 cookie +2026-03-02 19:52:37,997 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:52:38,014 - INFO - 设置 cookie: edentoken +2026-03-02 19:52:38,032 - INFO - 设置 cookie: user +2026-03-02 19:52:38,056 - INFO - 设置 cookie: userType +2026-03-02 19:52:53,369 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:52:53,618 - INFO - 已发送失败通知: 用户A - 网站A +2026-03-02 19:52:53,621 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-02 19:52:53,623 - INFO - 总网站数: 1 +2026-03-02 19:52:53,628 - INFO - 成功: 0, 失败: 1 +2026-03-02 19:52:53,630 - INFO - +失败详情: +2026-03-02 19:52:53,632 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:52:54,675 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-02 19:52:54,676 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 19:52:55,007 - INFO - 获取到 814 个域名,共 2940 个 cookie +2026-03-02 19:52:55,009 - INFO - +处理网站: 网站A +2026-03-02 19:52:55,011 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 19:52:55,012 - INFO - 找到 17 个 cookie +2026-03-02 19:52:58,102 - INFO - 开始设置 17 个 cookie +2026-03-02 19:52:58,116 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:52:58,131 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:52:58,145 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:52:58,161 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:52:58,178 - INFO - 设置 cookie: merchantType +2026-03-02 19:52:58,191 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:52:58,206 - INFO - 设置 cookie: JSESSIONID +2026-03-02 19:52:58,221 - INFO - 设置 cookie: _qimei_i_1 +2026-03-02 19:52:58,236 - INFO - 设置 cookie: merchantType +2026-03-02 19:52:58,251 - INFO - 设置 cookie: _qimei_h38 +2026-03-02 19:52:58,266 - INFO - 设置 cookie: _qimei_i_3 +2026-03-02 19:52:58,283 - INFO - 设置 cookie: _qimei_fingerprint +2026-03-02 19:52:58,299 - INFO - 设置 cookie: _qimei_uuid42 +2026-03-02 19:52:58,316 - INFO - 设置 cookie: LMKBIUSS +2026-03-02 19:52:58,332 - INFO - 设置 cookie: edentoken +2026-03-02 19:52:58,348 - INFO - 设置 cookie: user +2026-03-02 19:52:58,368 - INFO - 设置 cookie: userType +2026-03-02 19:53:12,951 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:53:13,096 - INFO - 已发送失败通知: 用户B - 网站A +2026-03-02 19:53:13,102 - INFO - +==================== 用户 用户B 处理结果汇总 ==================== +2026-03-02 19:53:13,104 - INFO - 总网站数: 1 +2026-03-02 19:53:13,107 - INFO - 成功: 0, 失败: 1 +2026-03-02 19:53:13,109 - INFO - +失败详情: +2026-03-02 19:53:13,110 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 19:53:14,193 - INFO - +================================================== +2026-03-02 19:53:14,194 - INFO - Cookie 监控结束 +2026-03-02 19:53:14,194 - INFO - ================================================== +2026-03-02 19:57:38,356 - INFO - ================================================== +2026-03-02 19:57:38,357 - INFO - Cookie 监控开始 +2026-03-02 19:57:38,357 - INFO - 时间: 2026-03-02 19:57:38 +2026-03-02 19:57:38,358 - INFO - ================================================== +2026-03-02 19:57:38,358 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-02 19:57:38,359 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 19:57:39,474 - INFO - 获取到 278 个域名,共 801 个 cookie +2026-03-02 19:57:39,475 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 19:57:50,386 - INFO - 开始导入 801 个 cookie +2026-03-02 19:59:53,803 - INFO - Cookie 导入完成: 成功 801, 失败 0 +2026-03-02 19:59:53,805 - INFO - 成功导入 801 个 cookie +2026-03-02 19:59:53,805 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-02 19:59:53,806 - INFO - +验证网站: 网站A +2026-03-02 19:59:53,806 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 20:00:09,296 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 20:00:09,298 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-02 20:00:09,299 - INFO - 总网站数: 1 +2026-03-02 20:00:09,299 - INFO - 成功: 0, 失败: 1 +2026-03-02 20:00:09,300 - INFO - +失败详情: +2026-03-02 20:00:09,301 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 20:00:10,370 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-02 20:00:10,374 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 20:00:10,940 - INFO - 获取到 814 个域名,共 2947 个 cookie +2026-03-02 20:00:10,948 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 20:00:14,614 - INFO - 开始导入 2947 个 cookie +2026-03-02 20:16:17,328 - INFO - ================================================== +2026-03-02 20:16:17,331 - INFO - Cookie 监控开始 +2026-03-02 20:16:17,331 - INFO - 时间: 2026-03-02 20:16:17 +2026-03-02 20:16:17,332 - INFO - ================================================== +2026-03-02 20:16:17,332 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-02 20:16:17,332 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 20:16:19,422 - INFO - 获取到 278 个域名,共 801 个 cookie +2026-03-02 20:16:19,423 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 20:16:33,797 - INFO - 开始导入 801 个 cookie +2026-03-02 20:30:29,314 - INFO - Cookie 导入完成: 成功 801, 失败 0 +2026-03-02 20:30:29,347 - INFO - 成功导入 801 个 cookie +2026-03-02 20:30:29,349 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-02 20:30:29,351 - INFO - +验证网站: 网站A +2026-03-02 20:30:29,352 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 20:31:15,938 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 20:31:16,156 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-02 20:31:16,174 - INFO - 总网站数: 1 +2026-03-02 20:31:16,188 - INFO - 成功: 0, 失败: 1 +2026-03-02 20:31:16,195 - INFO - +失败详情: +2026-03-02 20:31:16,204 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 20:31:17,772 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-02 20:31:17,832 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 20:31:19,351 - INFO - 获取到 814 个域名,共 2946 个 cookie +2026-03-02 20:31:19,469 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 20:31:40,232 - INFO - 开始导入 2946 个 cookie +2026-03-02 23:12:19,893 - INFO - ================================================== +2026-03-02 23:12:19,894 - INFO - Cookie 监控开始 +2026-03-02 23:12:19,894 - INFO - 时间: 2026-03-02 23:12:19 +2026-03-02 23:12:19,894 - INFO - ================================================== +2026-03-02 23:12:19,895 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-02 23:12:19,895 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 23:12:21,973 - INFO - 获取到 278 个域名,共 801 个 cookie +2026-03-02 23:12:21,973 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 23:12:26,570 - INFO - 开始导入 801 个 cookie +2026-03-02 23:14:33,586 - INFO - Cookie 导入完成: 成功 801, 失败 0 +2026-03-02 23:14:33,587 - INFO - 成功导入 801 个 cookie +2026-03-02 23:14:33,587 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-02 23:14:33,588 - INFO - +验证网站: 网站A +2026-03-02 23:14:33,588 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 23:14:52,444 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:14:52,659 - INFO - 已发送失败通知: 用户A - 网站A +2026-03-02 23:14:52,660 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-02 23:14:52,661 - INFO - 总网站数: 1 +2026-03-02 23:14:52,661 - INFO - 成功: 0, 失败: 1 +2026-03-02 23:14:52,662 - INFO - +失败详情: +2026-03-02 23:14:52,662 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:14:53,782 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-02 23:14:53,782 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 23:14:54,433 - INFO - 获取到 814 个域名,共 2950 个 cookie +2026-03-02 23:14:54,433 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 23:14:58,023 - INFO - 开始导入 2950 个 cookie +2026-03-02 23:22:13,589 - INFO - Cookie 导入完成: 成功 2949, 失败 1 +2026-03-02 23:22:13,690 - WARNING - 部分 cookie 导入失败 +2026-03-02 23:22:13,701 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-02 23:22:13,704 - INFO - +验证网站: 网站A +2026-03-02 23:22:13,706 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 23:22:13,709 - ERROR - 验证登录状态失败: +与页面的连接已断开。 +版本: 4.1.0.18 +2026-03-02 23:22:13,710 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:22:13,720 - INFO - +==================== 用户 用户B 处理结果汇总 ==================== +2026-03-02 23:22:13,732 - INFO - 总网站数: 1 +2026-03-02 23:22:13,748 - INFO - 成功: 0, 失败: 1 +2026-03-02 23:22:13,755 - INFO - +失败详情: +2026-03-02 23:22:13,759 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:22:14,205 - INFO - +================================================== +2026-03-02 23:22:14,206 - INFO - Cookie 监控结束 +2026-03-02 23:22:14,206 - INFO - ================================================== +2026-03-02 23:24:45,469 - INFO - ================================================== +2026-03-02 23:24:45,470 - INFO - Cookie 监控开始 +2026-03-02 23:24:45,470 - INFO - 时间: 2026-03-02 23:24:45 +2026-03-02 23:24:45,471 - INFO - ================================================== +2026-03-02 23:24:45,471 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-02 23:24:45,472 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 23:24:51,705 - INFO - 获取到 278 个域名,共 801 个 cookie +2026-03-02 23:24:51,705 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 23:25:00,772 - INFO - 开始导入 801 个 cookie +2026-03-02 23:26:59,941 - INFO - Cookie 导入完成: 成功 801, 失败 0 +2026-03-02 23:26:59,942 - INFO - 成功导入 801 个 cookie +2026-03-02 23:26:59,942 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-02 23:26:59,943 - INFO - +验证网站: 网站A +2026-03-02 23:26:59,943 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 23:27:14,779 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:27:14,783 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-02 23:27:14,785 - INFO - 总网站数: 1 +2026-03-02 23:27:14,786 - INFO - 成功: 0, 失败: 1 +2026-03-02 23:27:14,787 - INFO - +失败详情: +2026-03-02 23:27:14,788 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:27:15,886 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-02 23:27:15,889 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-02 23:27:16,629 - INFO - 获取到 814 个域名,共 2948 个 cookie +2026-03-02 23:27:16,631 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-02 23:27:18,380 - INFO - 开始导入 2948 个 cookie +2026-03-02 23:31:37,244 - INFO - Cookie 导入完成: 成功 2947, 失败 1 +2026-03-02 23:31:37,246 - WARNING - 部分 cookie 导入失败 +2026-03-02 23:31:37,247 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-02 23:31:37,248 - INFO - +验证网站: 网站A +2026-03-02 23:31:37,248 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-02 23:31:37,254 - ERROR - 验证登录状态失败: +与页面的连接已断开。 +版本: 4.1.0.18 +2026-03-02 23:31:37,256 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:31:37,260 - INFO - +==================== 用户 用户B 处理结果汇总 ==================== +2026-03-02 23:31:37,263 - INFO - 总网站数: 1 +2026-03-02 23:31:37,264 - INFO - 成功: 0, 失败: 1 +2026-03-02 23:31:37,264 - INFO - +失败详情: +2026-03-02 23:31:37,265 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-02 23:31:38,233 - INFO - +================================================== +2026-03-02 23:31:38,235 - INFO - Cookie 监控结束 +2026-03-02 23:31:38,236 - INFO - ================================================== +2026-03-03 08:29:26,844 - INFO - ================================================== +2026-03-03 08:29:26,845 - INFO - Cookie 监控开始 +2026-03-03 08:29:26,845 - INFO - 时间: 2026-03-03 08:29:26 +2026-03-03 08:29:26,846 - INFO - ================================================== +2026-03-03 08:29:26,846 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-03 08:29:26,846 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-03 08:29:33,515 - INFO - 获取到 278 个域名,共 801 个 cookie +2026-03-03 08:29:33,515 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-03 08:29:34,818 - INFO - 开始导入 801 个 cookie +2026-03-03 08:34:05,206 - INFO - Cookie 导入完成: 成功 800, 失败 1 +2026-03-03 08:34:05,227 - WARNING - 部分 cookie 导入失败 +2026-03-03 08:34:05,228 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-03 08:34:05,234 - INFO - +验证网站: 网站A +2026-03-03 08:34:05,236 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-03 08:34:05,239 - ERROR - 验证登录状态失败: +与页面的连接已断开。 +版本: 4.1.0.18 +2026-03-03 08:34:05,240 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:34:05,255 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-03 08:34:05,259 - INFO - 总网站数: 1 +2026-03-03 08:34:05,261 - INFO - 成功: 0, 失败: 1 +2026-03-03 08:34:05,270 - INFO - +失败详情: +2026-03-03 08:34:05,271 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:34:05,974 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-03 08:34:05,976 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-03 08:34:06,647 - INFO - 获取到 813 个域名,共 2940 个 cookie +2026-03-03 08:34:06,648 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-03 08:34:09,040 - INFO - 开始导入 2940 个 cookie +2026-03-03 08:47:28,987 - INFO - Cookie 导入完成: 成功 2939, 失败 1 +2026-03-03 08:47:29,120 - WARNING - 部分 cookie 导入失败 +2026-03-03 08:47:29,235 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-03 08:47:29,271 - INFO - +验证网站: 网站A +2026-03-03 08:47:29,313 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-03 08:47:29,334 - ERROR - 验证登录状态失败: +与页面的连接已断开。 +版本: 4.1.0.18 +2026-03-03 08:47:29,359 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:47:30,226 - INFO - 已发送失败通知: 用户B - 网站A +2026-03-03 08:47:30,227 - INFO - +==================== 用户 用户B 处理结果汇总 ==================== +2026-03-03 08:47:30,229 - INFO - 总网站数: 1 +2026-03-03 08:47:30,230 - INFO - 成功: 0, 失败: 1 +2026-03-03 08:47:30,231 - INFO - +失败详情: +2026-03-03 08:47:30,234 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:47:30,235 - INFO - +================================================== +2026-03-03 08:47:30,236 - INFO - Cookie 监控结束 +2026-03-03 08:47:30,237 - INFO - ================================================== +2026-03-03 08:47:50,074 - INFO - ================================================== +2026-03-03 08:47:50,075 - INFO - Cookie 监控开始 +2026-03-03 08:47:50,076 - INFO - 时间: 2026-03-03 08:47:50 +2026-03-03 08:47:50,076 - INFO - ================================================== +2026-03-03 08:47:50,077 - INFO - ==================== 开始处理用户: 用户A ==================== +2026-03-03 08:47:50,077 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-03 08:47:56,370 - INFO - 获取到 278 个域名,共 801 个 cookie +2026-03-03 08:47:56,371 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-03 08:48:03,542 - INFO - 开始导入 801 个 cookie +2026-03-03 08:49:19,995 - INFO - Cookie 导入完成: 成功 800, 失败 1 +2026-03-03 08:49:19,995 - WARNING - 部分 cookie 导入失败 +2026-03-03 08:49:19,996 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-03 08:49:19,996 - INFO - +验证网站: 网站A +2026-03-03 08:49:19,997 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-03 08:49:20,040 - ERROR - 验证登录状态失败: +与页面的连接已断开。 +版本: 4.1.0.18 +2026-03-03 08:49:20,042 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:49:22,005 - INFO - 已发送失败通知: 用户A - 网站A +2026-03-03 08:49:22,007 - INFO - +==================== 用户 用户A 处理结果汇总 ==================== +2026-03-03 08:49:22,007 - INFO - 总网站数: 1 +2026-03-03 08:49:22,008 - INFO - 成功: 0, 失败: 1 +2026-03-03 08:49:22,008 - INFO - +失败详情: +2026-03-03 08:49:22,009 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:49:22,009 - INFO - ==================== 开始处理用户: 用户B ==================== +2026-03-03 08:49:22,009 - INFO - 步骤1: 从 Cookie Cloud 获取所有 cookies +2026-03-03 08:49:22,809 - INFO - 获取到 813 个域名,共 2940 个 cookie +2026-03-03 08:49:22,819 - INFO - 步骤2: 一次性导入所有 cookies 到浏览器 +2026-03-03 08:49:30,875 - INFO - 开始导入 2940 个 cookie +2026-03-03 08:49:43,208 - INFO - Cookie 导入完成: 成功 2939, 失败 1 +2026-03-03 08:49:43,209 - WARNING - 部分 cookie 导入失败 +2026-03-03 08:49:43,209 - INFO - 步骤3: 依次验证各网站登录状态 +2026-03-03 08:49:43,209 - INFO - +验证网站: 网站A +2026-03-03 08:49:43,210 - INFO - URL: https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll +2026-03-03 08:49:43,212 - ERROR - 验证登录状态失败: +与页面的连接已断开。 +版本: 4.1.0.18 +2026-03-03 08:49:43,214 - WARNING - ✗ 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:49:43,219 - INFO - +==================== 用户 用户B 处理结果汇总 ==================== +2026-03-03 08:49:43,223 - INFO - 总网站数: 1 +2026-03-03 08:49:43,227 - INFO - 成功: 0, 失败: 1 +2026-03-03 08:49:43,236 - INFO - +失败详情: +2026-03-03 08:49:43,236 - INFO - - 网站A: 登录状态验证失败(cookies可能已过期或选择器配置错误) +2026-03-03 08:49:43,691 - INFO - +================================================== +2026-03-03 08:49:43,692 - INFO - Cookie 监控结束 +2026-03-03 08:49:43,692 - INFO - ================================================== diff --git a/moviepilot_cookie_demo.py b/moviepilot_cookie_demo.py new file mode 100644 index 0000000..59727fd --- /dev/null +++ b/moviepilot_cookie_demo.py @@ -0,0 +1,394 @@ +""" +MoviePilot Cookie功能示例程序 +从CookieCloud下载数据并注入浏览器访问网站 +""" + +import json +import time +import hashlib +import base64 +from typing import Dict, List, Optional, Tuple +from dataclasses import dataclass +import urllib.request +import urllib.error +from Crypto.Cipher import AES + + +@dataclass +class CookieCloudConfig: + """CookieCloud配置""" + server: str + username: str + password: str + timeout: int = 30 + + +class CookieCloudDownloader: + """从CookieCloud服务器下载cookie数据""" + + def __init__(self, config: CookieCloudConfig): + self.config = config + + def _get_crypt_key(self) -> bytes: + combined_string = f"{self.config.username}-{self.config.password}" + return hashlib.md5(combined_string.encode('utf-8')).hexdigest()[:16].encode("utf-8") + + def _bytes_to_key(self, data: bytes, salt: bytes, output: int = 48) -> bytes: + 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] + + 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 _get_url_domain(self, domain: str) -> str: + if not domain: + return "" + domain = domain.lstrip('.') + if ":" in domain: + domain = domain.split(":")[0] + parts = domain.split(".") + if all(part.isdigit() for part in parts): + return domain + if len(parts) >= 2: + return ".".join(parts[-2:]) + return domain + + def download_all(self) -> Tuple[Optional[Dict], str]: + """下载所有cookie和local storage数据""" + try: + url = f"{self.config.server}/get/{self.config.username}" + request = urllib.request.Request( + url, + headers={ + 'Content-Type': 'application/json', + 'User-Agent': 'MoviePilot-Cookie-Client/1.0' + }, + method='GET' + ) + + response = urllib.request.urlopen(request, timeout=self.config.timeout) + + 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: + 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解密为空" + + cookie_data = result.get("cookie_data", {}) + local_storage_data = result.get("local_storage_data", {}) + + total_cookies = sum(len(v) for v in cookie_data.values()) + total_ls = sum(len(v) for v in local_storage_data.values()) + print(f" 下载完成: {len(cookie_data)} 个域名的Cookie({total_cookies}个), {len(local_storage_data)} 个域名的Local Storage({total_ls}个)") + + return {"cookies": cookie_data, "local_storage": local_storage_data}, "" + + except urllib.error.HTTPError as e: + if e.code == 401: + return None, "认证失败,请检查用户名和密码" + else: + 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)}" + + +class BrowserController: + """使用Playwright控制浏览器""" + + def __init__(self, headless: bool = False, use_edge: bool = False): + self.headless = headless + self.use_edge = use_edge + self.browser = None + self.context = None + self.page = None + self.playwright = None + + def start(self) -> bool: + try: + from playwright.sync_api import sync_playwright + + self.playwright = sync_playwright().start() + + if self.use_edge: + self.browser = self.playwright.chromium.launch( + channel="msedge", + headless=self.headless, + args=['--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage', '--no-sandbox'] + ) + else: + self.browser = self.playwright.chromium.launch( + headless=self.headless, + args=['--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage', '--no-sandbox'] + ) + + self.context = self.browser.new_context( + user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' + ) + + self.page = self.context.new_page() + + print(f"✓ 浏览器启动成功 ({'Edge' if self.use_edge else 'Chromium'})") + return True + + except Exception as e: + print(f"✗ 浏览器启动失败: {str(e)}") + return False + + def inject_cookies(self, cookies: Dict): + """注入cookie到浏览器""" + if not self.context or not cookies: + return + + try: + playwright_cookies = [] + for domain_key, cookie_list in cookies.items(): + for cookie in cookie_list: + cookie_domain = cookie.get("domain", "").lstrip('.') + if not cookie_domain: + cookie_domain = domain_key.lstrip('.') + + pw_cookie = { + "name": cookie.get("name", ""), + "value": cookie.get("value", ""), + "domain": cookie_domain, + "path": cookie.get("path", "/"), + } + if cookie.get("secure"): + pw_cookie["secure"] = True + if cookie.get("httpOnly"): + pw_cookie["httpOnly"] = True + if cookie.get("expirationDate"): + pw_cookie["expires"] = int(cookie.get("expirationDate")) + playwright_cookies.append(pw_cookie) + + self.context.add_cookies(playwright_cookies) + print(f"✓ Cookie注入成功 ({len(playwright_cookies)} 个)") + except Exception as e: + print(f"✗ Cookie注入失败: {str(e)}") + + def navigate(self, url: str, wait_time: int = 5, timeout: int = 60) -> bool: + if not self.page: + return False + + try: + print(f"正在访问: {url}") + + self.page.goto(url, timeout=timeout * 1000) + + try: + self.page.wait_for_load_state("networkidle", timeout=timeout * 1000) + except Exception: + print(" networkidle超时,尝试load状态...") + try: + self.page.wait_for_load_state("load", timeout=30000) + except Exception: + print(" load超时,尝试domcontentloaded状态...") + self.page.wait_for_load_state("domcontentloaded", timeout=10000) + + time.sleep(wait_time) + + print(f"✓ 页面加载完成") + return True + + except Exception as e: + print(f"✗ 页面访问失败: {str(e)}") + return False + + def check_login_status(self, login_indicator: str) -> bool: + if not self.page: + return False + + try: + html_content = self.page.content() + + if login_indicator: + if login_indicator in html_content: + print(f"✓ 检测到登录指示器: {login_indicator}") + return True + else: + print(f"✗ 未检测到登录指示器: {login_indicator}") + return False + + return False + + except Exception as e: + print(f"✗ 登录状态检查失败: {str(e)}") + return False + + def take_screenshot(self, filepath: str): + if not self.page: + return + + try: + self.page.screenshot(path=filepath) + print(f"✓ 截图已保存: {filepath}") + except Exception as e: + print(f"✗ 截图失败: {str(e)}") + + def close(self): + try: + if self.browser: + self.browser.close() + + if self.playwright: + self.playwright.stop() + + print("✓ 浏览器已关闭") + + except Exception as e: + print(f"✗ 关闭浏览器失败: {str(e)}") + + +class CookieDemo: + """Cookie功能示例""" + + def __init__(self, cookiecloud_config: CookieCloudConfig): + self.downloader = CookieCloudDownloader(cookiecloud_config) + self.browser = None + + def run_sites(self, sites: list, headless: bool = False, use_edge: bool = False): + """下载一次数据,依次访问多个网站""" + # 下载数据 + print("=" * 60) + print("从CookieCloud下载数据") + print("=" * 60) + + data, error = self.downloader.download_all() + + if error: + print(f"✗ 数据下载失败: {error}") + return + + # 启动浏览器 + print(f"\n启动浏览器") + print("-" * 60) + + self.browser = BrowserController(headless=headless, use_edge=use_edge) + + if not self.browser.start(): + return + + # 注入所有cookie + if data and data.get("cookies"): + print(f"\n注入所有cookie到浏览器") + print("-" * 60) + self.browser.inject_cookies(data["cookies"]) + + # 依次访问每个网站 + for idx, site in enumerate(sites, 1): + print(f"\n\n{'=' * 60}") + print(f"访问网站 {idx}/{len(sites)}: {site['name']}") + print(f"URL: {site['url']}") + print("=" * 60) + + if not self.browser.navigate(site['url']): + print(f"✗ 页面访问失败") + continue + + # 验证登录状态 + from urllib.parse import urlparse + parsed = urlparse(site['url']) + target_domain = parsed.netloc + + login_indicator = site.get('login_indicator') + is_logged_in = self.browser.check_login_status(login_indicator) + + if is_logged_in: + print("✓ Cookie验证成功:已登录状态") + else: + print("⚠ Cookie验证失败:未检测到登录状态") + + # 截图 + screenshot_path = f"{target_domain.replace('.', '_').replace(':', '_')}_screenshot.png" + self.browser.take_screenshot(screenshot_path) + + if idx < len(sites): + print("\n等待3秒后继续下一个网站...") + time.sleep(3) + + # 关闭浏览器 + print(f"\n关闭浏览器") + print("-" * 60) + self.browser.close() + + print("\n" + "=" * 60) + print("所有网站访问完成") + print("=" * 60) + + +def main(): + # 配置CookieCloud + config = CookieCloudConfig( + server="http://192.168.1.100:3000/cookiecloud", + username="qyZpTrgiP5mVwiRZwfBhjz", + password="iGn1B4FnWftp3oj4Ko3jxA", + timeout=30 + ) + + # 创建示例程序 + demo = CookieDemo(config) + + # 测试网站列表 + test_sites = [ + { + "url": "https://lmkbi.95155.com/bi-system/#/login", + "name": "BI系统", + "login_indicator": "彭峰" + }, + { + "url": "http://192.168.1.100:3000/#/subscribe/movie", + "name": "MoviePilot订阅", + "login_indicator": "搜索" + }, + { + "url": "http://192.168.1.102:8418/bwadmin/QueryCarInfo2", + "name": "QueryCarInfo2", + "login_indicator": "工单管理" + } + ] + + # 下载一次数据,依次访问所有网站 + demo.run_sites( + sites=test_sites, + headless=False, + use_edge=True + ) + + +if __name__ == "__main__": + main() diff --git a/state.json b/state.json index e474d5d..5f3b5ad 100644 --- a/state.json +++ b/state.json @@ -1,3 +1,4 @@ { - "用户A_网站A": 2 + "用户A_网站A": 0, + "用户B_网站A": 1 } \ No newline at end of file