Initial commit

This commit is contained in:
2026-03-02 17:02:09 +08:00
commit f8a14a3e2e
16 changed files with 1845 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
"""Cookie Cloud 模块 - 从 Cookie Cloud 服务器获取并解密 cookie 数据"""
import hashlib
import json
import base64
from typing import Dict, List, Optional
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from applogger import get_logger
logger = get_logger(__name__)
class CookieCloud:
"""Cookie Cloud 客户端"""
def __init__(self, api_url: str, uuid: str, password: str):
"""
初始化 Cookie Cloud 客户端
Args:
api_url: Cookie Cloud API 地址
uuid: 用户 UUID
password: 用户密码
"""
self.api_url = api_url.rstrip('/')
self.uuid = uuid
self.password = password
def decrypt(self, encrypted_data: str) -> Dict:
"""
解密 Cookie Cloud 数据
Args:
encrypted_data: 加密的数据字符串
Returns:
解密后的数据字典,包含 cookie_data 和 local_storage_data
"""
# 生成解密密钥: md5(uuid+password) 取前 16 个字符
key_str = f"{self.uuid}{self.password}"
key = hashlib.md5(key_str.encode()).hexdigest()[:16].encode()
print(f"生成的密钥: {key}")
print(f"密钥长度: {len(key)}")
# 解码 Base64
encrypted = base64.b64decode(encrypted_data)
print(f"Base64 解码后长度: {len(encrypted)}")
print(f"解码后前 20 字节: {encrypted[:20]}")
# 检查是否有盐值(OpenSSL 格式)
if encrypted.startswith(b'Salted__'):
print("检测到 OpenSSL 格式数据")
# 对于 OpenSSL 格式,我们需要跳过盐值部分
# 但根据用户要求,我们直接使用16字符密钥,不考虑盐值
ciphertext = encrypted[16:] # 跳过 "Salted__" 和盐值
print(f"密文长度: {len(ciphertext)}")
else:
print("未检测到 OpenSSL 格式,使用整个数据作为密文")
ciphertext = encrypted
# 使用固定的 IV (16 个 0 字节)
iv = b'\x00' * 16
print(f"使用的 IV: {iv}")
# AES-128-CBC 解密(16字节密钥)
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(ciphertext)
print(f"解密后长度: {len(decrypted)}")
print(f"解密后前 100 字节: {decrypted[:100]}")
try:
# 移除 PKCS#7 填充
decrypted = unpad(decrypted, AES.block_size)
print(f"移除填充后长度: {len(decrypted)}")
print(f"移除填充后前 100 字节: {decrypted[:100]}")
# 尝试解析 JSON
result = json.loads(decrypted.decode('utf-8'))
print("JSON 解析成功")
return result
except Exception as e:
print(f"解密失败: {e}")
# 尝试直接截取可能的有效数据
try:
# 尝试找到 JSON 开始的位置
for i in range(len(decrypted)):
try:
test_data = decrypted[i:]
test_str = test_data.decode('utf-8', errors='ignore')
if test_str.strip().startswith('{'):
print(f"找到可能的 JSON 开始位置: {i}")
result = json.loads(test_str)
print("JSON 解析成功(直接截取)")
return result
except:
continue
except:
pass
raise
def get_cookies(self) -> Dict[str, List[Dict]]:
"""
从 Cookie Cloud 服务器获取 cookie 数据
Returns:
按域名分组的 cookie 数据字典
格式: {"domain.com": [{"name": "cookie1", "value": "val1", ...}, ...], ...}
"""
url = f"{self.api_url}/get/{self.uuid}"
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
data = response.json()
logger.info(f"Cookie Cloud 响应: {json.dumps(data, indent=2)}")
if not data or 'encrypted' not in data:
logger.error("响应中没有 encrypted 字段")
return {}
# 打印完整的加密数据长度和前200个字符
logger.info(f"加密数据长度: {len(data['encrypted'])}")
logger.info(f"加密数据前 200 个字符: {data['encrypted'][:200]}...")
try:
decrypted_data = self.decrypt(data['encrypted'])
logger.info(f"解密后的数据: {json.dumps(decrypted_data, indent=2)}")
except Exception as e:
logger.error(f"解密失败: {e}")
return {}
# 解析 cookie_data
cookie_dict = {}
if 'cookie_data' in decrypted_data:
for domain, cookies in decrypted_data['cookie_data'].items():
cookie_dict[domain] = []
for cookie in cookies:
# 处理 sameSite 字段
if cookie.get('sameSite') == 'unspecified':
cookie['sameSite'] = 'Lax'
cookie_dict[domain].append(cookie)
return cookie_dict
except requests.RequestException as e:
logger.error(f"请求 Cookie Cloud 失败: {e}")
return {}
except Exception as e:
logger.error(f"处理 Cookie Cloud 数据失败: {e}")
return {}
def get_cookies_for_domain(self, domain: str) -> List[Dict]:
"""
获取指定域名的 cookie
Args:
domain: 域名
Returns:
cookie 列表
"""
all_cookies = self.get_cookies()
# 查找匹配的域名
for cookie_domain, cookies in all_cookies.items():
if domain in cookie_domain or cookie_domain in domain:
return cookies
return []
if __name__ == "__main__":
# 测试代码
cc = CookieCloud(
api_url="https://movie-pilot.org/cookiecloud",
uuid="hu6n2vcUmzpu7mqUN2rVCg",
password="jtDM5dV9AyqXkZdQVeA9f6"
)
cookies = cc.get_cookies()
logger.info(json.dumps(cookies, indent=2, ensure_ascii=False))