add-files

This commit is contained in:
2026-03-03 08:58:16 +08:00
parent 0fbbbce2ee
commit 894acc3191
23 changed files with 2492 additions and 1 deletions
+347
View File
@@ -0,0 +1,347 @@
"""
CookieCloud客户端核心实现
"""
import json
import time
import urllib.request
import urllib.error
from typing import Dict, List, Optional, Tuple
from .models import CookieConfig, CookieData, CookieCollection, DownloadResult
from .exceptions import (
CookieCloudError,
ConfigurationError,
ConnectionError,
AuthenticationError,
DataParseError,
NetworkError
)
class CookieCloudClient:
"""
CookieCloud客户端
用于从CookieCloud服务器下载和管理cookies的独立客户端
示例:
>>> 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