177 lines
5.9 KiB
Python
177 lines
5.9 KiB
Python
"""Cookie Cloud 模块 - 从 Cookie Cloud 服务器获取 cookie 数据"""
|
|
import json
|
|
import hashlib
|
|
import base64
|
|
from typing import Dict, List, Optional, Tuple
|
|
import urllib.request
|
|
import urllib.error
|
|
from Crypto.Cipher import AES
|
|
from applogger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class CookieCloud:
|
|
"""Cookie Cloud 客户端"""
|
|
|
|
def __init__(self, api_url: str, uuid: str, password: str):
|
|
"""
|
|
初始化 Cookie Cloud 客户端
|
|
|
|
Args:
|
|
api_url: Cookie Cloud API 地址
|
|
uuid: 用户 UUID
|
|
password: 用户密码
|
|
"""
|
|
self.api_url = api_url.rstrip('/')
|
|
self.uuid = uuid
|
|
self.password = password
|
|
|
|
def _get_crypt_key(self) -> bytes:
|
|
"""生成加密密钥"""
|
|
combined_string = f"{self.uuid}-{self.password}"
|
|
return hashlib.md5(combined_string.encode('utf-8')).hexdigest()[:16].encode("utf-8")
|
|
|
|
def _bytes_to_key(self, data: bytes, salt: bytes, output: int = 48) -> bytes:
|
|
"""OpenSSL EVP_BytesToKey 密钥派生算法"""
|
|
assert len(salt) == 8, len(salt)
|
|
data += salt
|
|
key = hashlib.md5(data).digest()
|
|
final_key = key
|
|
while len(final_key) < output:
|
|
key = hashlib.md5(key + data).digest()
|
|
final_key += key
|
|
return final_key[:output]
|
|
|
|
def _decrypt(self, encrypted: str, passphrase: bytes) -> bytes:
|
|
"""解密数据"""
|
|
encrypted_bytes = base64.b64decode(encrypted)
|
|
assert encrypted_bytes.startswith(b"Salted__"), "Invalid encrypted data format"
|
|
salt = encrypted_bytes[8:16]
|
|
key_iv = self._bytes_to_key(passphrase, salt, 32 + 16)
|
|
key = key_iv[:32]
|
|
iv = key_iv[32:]
|
|
aes = AES.new(key, AES.MODE_CBC, iv)
|
|
decrypted_padded = aes.decrypt(encrypted_bytes[16:])
|
|
padding_length = decrypted_padded[-1]
|
|
if isinstance(padding_length, str):
|
|
padding_length = ord(padding_length)
|
|
return decrypted_padded[:-padding_length]
|
|
|
|
def _download_data(self) -> Tuple[Optional[Dict], str]:
|
|
"""下载并解密所有数据"""
|
|
try:
|
|
url = f"{self.api_url}/get/{self.uuid}"
|
|
request = urllib.request.Request(
|
|
url,
|
|
headers={
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': 'CookieCloud-Client/1.0'
|
|
},
|
|
method='GET'
|
|
)
|
|
|
|
response = urllib.request.urlopen(request, timeout=30)
|
|
|
|
if response.status != 200:
|
|
return None, f"服务器返回错误状态码: {response.status}"
|
|
|
|
result = json.loads(response.read().decode('utf-8'))
|
|
|
|
if not result:
|
|
return None, "服务器返回数据为空"
|
|
|
|
encrypted = result.get("encrypted")
|
|
if not encrypted:
|
|
return None, "未获取到cookie密文"
|
|
|
|
crypt_key = self._get_crypt_key()
|
|
try:
|
|
decrypted_data = self._decrypt(encrypted, crypt_key)
|
|
result = json.loads(decrypted_data.decode("utf-8"))
|
|
except Exception as e:
|
|
return None, f"cookie解密失败: {str(e)}"
|
|
|
|
if not result:
|
|
return None, "cookie解密为空"
|
|
|
|
return result, ""
|
|
|
|
except urllib.error.HTTPError as e:
|
|
return None, f"HTTP错误: {e.code} {e.reason}"
|
|
except urllib.error.URLError as e:
|
|
return None, f"网络连接失败: {e.reason}"
|
|
except Exception as e:
|
|
return None, f"下载失败: {str(e)}"
|
|
|
|
def get_cookies(self) -> Dict[str, List[Dict]]:
|
|
"""
|
|
从 Cookie Cloud 服务器获取 cookie 数据
|
|
|
|
Returns:
|
|
按域名分组的 cookie 数据字典
|
|
格式: {"domain.com": [{"name": "cookie1", "value": "val1", ...}, ...], ...}
|
|
"""
|
|
data, error = self._download_data()
|
|
|
|
if error:
|
|
logger.error(error)
|
|
return {}
|
|
|
|
# 处理数据结构
|
|
cookie_data = {}
|
|
|
|
# 兼容直接按域名分组的格式
|
|
if isinstance(data, dict) and not data.get('cookie_data'):
|
|
cookie_data = data
|
|
# 兼容包含 cookie_data 的格式
|
|
elif isinstance(data, dict) and data.get('cookie_data'):
|
|
cookie_data = data.get('cookie_data', {})
|
|
|
|
# 处理 sameSite 字段
|
|
processed_cookies = {}
|
|
for domain, cookies in cookie_data.items():
|
|
if not cookies:
|
|
continue
|
|
processed_cookies[domain] = []
|
|
for cookie in cookies:
|
|
if cookie.get('sameSite') == 'unspecified':
|
|
cookie['sameSite'] = 'Lax'
|
|
processed_cookies[domain].append(cookie)
|
|
|
|
return processed_cookies
|
|
|
|
def get_cookies_for_domain(self, domain: str) -> List[Dict]:
|
|
"""
|
|
获取指定域名的 cookie
|
|
|
|
Args:
|
|
domain: 域名
|
|
|
|
Returns:
|
|
cookie 列表
|
|
"""
|
|
all_cookies = self.get_cookies()
|
|
|
|
matched_cookies = []
|
|
for cookie_domain, cookies in all_cookies.items():
|
|
clean_domain = cookie_domain.lstrip('.')
|
|
if domain in clean_domain or clean_domain in domain:
|
|
matched_cookies.extend(cookies)
|
|
|
|
return matched_cookies
|
|
|
|
|
|
if __name__ == "__main__":
|
|
cc = CookieCloud(
|
|
api_url="https://movie-pilot.org/cookiecloud",
|
|
uuid="hu6n2vcUmzpu7mqUN2rVCg",
|
|
password="jtDM5dV9AyqXkZdQVeA9f6"
|
|
)
|
|
|
|
cookies = cc.get_cookies()
|
|
logger.info(f"获取到 {len(cookies)} 个域名的 cookies")
|
|
|
|
lmkbi_cookies = cc.get_cookies_for_domain('lmkbi.95155.com')
|
|
logger.info(f"获取到 {len(lmkbi_cookies)} 个 lmkbi.95155.com 的 cookies")
|