import requests import re import sys import json import time import random import zlib def get_timestamp(): """获取当前时间戳(毫秒)""" return str(int(time.time() * 1000)) def crc32(data): """计算CRC32并转换为16进制""" crc = zlib.crc32(data.encode()) & 0xffffffff return format(crc, '08x') def hex_to_int(hex_str): """16进制转10进制""" return int(hex_str, 16) def encode123(url, way, version, timestamp): """ 123盘的URL加密算法 参考C++代码中的encode123函数 """ # 生成随机数 a = int(10000000 * random.randint(1, 10000000) / 10000) # 字符映射表 u = "adefghlmyijnopkqrstubcvwsz" # 将时间戳转换为时间格式 time_long = int(timestamp) // 1000 time_struct = time.localtime(time_long) time_str = time.strftime("%Y%m%d%H%M", time_struct) # 根据时间字符串生成g g = "" for char in time_str: digit = int(char) if digit == 0: g += u[0] else: # 修正:数字1对应索引0,数字2对应索引1,以此类推 g += u[digit - 1] # 计算y值(CRC32的十进制) y = str(hex_to_int(crc32(g))) # 计算最终的CRC32 final_crc_input = f"{time_long}|{a}|{url}|{way}|{version}|{y}" final_crc = str(hex_to_int(crc32(final_crc_input))) # 返回加密后的URL参数 return f"?{y}={time_long}-{a}-{final_crc}" def login_123pan(username, password): """登录123盘获取token""" print(f"🔐 正在登录账号: {username}") login_data = { "passport": username, "password": password, "remember": True } try: response = requests.post( "https://login.123pan.com/api/user/sign_in", json=login_data, timeout=30 ) result = response.json() if result.get('code') == 200: token = result.get('data', {}).get('token', '') print(f"✅ 登录成功!") return token else: error_msg = result.get('message', '未知错误') print(f"❌ 登录失败: {error_msg}") return None except Exception as e: print(f"❌ 登录请求失败: {e}") return None def get_share_info(share_key, password=''): """获取分享信息(不需要登录)""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.123pan.com/', 'Origin': 'https://www.123pan.com', } api_url = f"https://www.123pan.com/b/api/share/get?limit=100&next=1&orderBy=share_id&orderDirection=desc&shareKey={share_key}&SharePwd={password}&ParentFileId=0&Page=1" try: response = requests.get(api_url, headers=headers, timeout=30) return response.json() except Exception as e: print(f"❌ 获取分享信息失败: {e}") return None def get_download_url_android(file_info, token): """ 使用Android平台API获取下载链接(关键方法) 参考C++代码中的逻辑 """ # 🔥 关键:使用Android平台的请求头 headers = { 'App-Version': '55', 'platform': 'android', 'Authorization': f'Bearer {token}', 'User-Agent': 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36', 'Content-Type': 'application/json', } # 构建请求数据 post_data = { 'driveId': 0, 'etag': file_info.get('Etag', ''), 'fileId': file_info.get('FileId'), 'fileName': file_info.get('FileName', ''), 's3keyFlag': file_info.get('S3KeyFlag', ''), 'size': file_info.get('Size'), 'type': 0 } # 🔥 关键:使用encode123加密URL参数 timestamp = get_timestamp() encrypted_params = encode123('/b/api/file/download_info', 'android', '55', timestamp) api_url = f"https://www.123pan.com/b/api/file/download_info{encrypted_params}" print(f" 📡 API URL: {api_url[:80]}...") try: response = requests.post(api_url, json=post_data, headers=headers, timeout=30) result = response.json() print(f" 📥 API响应: code={result.get('code')}, message={result.get('message', 'N/A')}") if result.get('code') == 0 and 'data' in result: download_url = result['data'].get('DownloadUrl') or result['data'].get('DownloadURL') return download_url else: error_msg = result.get('message', '未知错误') print(f" ✗ API返回错误: {error_msg}") return None except Exception as e: print(f" ✗ 请求失败: {e}") import traceback traceback.print_exc() return None def start(link, password='', username='', user_password=''): """主函数:解析123盘分享链接""" result = { 'code': 200, 'data': [], 'need_login': False } # 提取 Share_Key patterns = [ r'/s/(.*?)\.html', r'/s/([^/\s]+)', ] share_key = None for pattern in patterns: matches = re.findall(pattern, link) if matches: share_key = matches[0] break if not share_key: return { "code": 201, "message": "分享地址错误,无法提取分享密钥" } print(f"📌 分享密钥: {share_key}") # 如果提供了账号密码,先登录 token = None if username and user_password: token = login_123pan(username, user_password) if not token: return { "code": 201, "message": "登录失败" } else: print("⚠️ 未提供登录信息,某些文件可能无法下载") # 获取分享信息 print(f"\n📂 正在获取文件列表...") share_data = get_share_info(share_key, password) if not share_data or share_data.get('code') != 0: error_msg = share_data.get('message', '未知错误') if share_data else '请求失败' return { "code": 201, "message": f"获取分享信息失败: {error_msg}" } # 获取文件列表 if 'data' not in share_data or 'InfoList' not in share_data['data']: return { "code": 201, "message": "返回数据格式错误" } info_list = share_data['data']['InfoList'] length = len(info_list) print(f"📁 找到 {length} 个项目\n") # 遍历文件列表 for i, file_info in enumerate(info_list): file_type = file_info.get('Type', 0) file_name = file_info.get('FileName', '') # 跳过文件夹 if file_type != 0: print(f"[{i+1}/{length}] 跳过文件夹: {file_name}") continue print(f"[{i+1}/{length}] 正在解析: {file_name}") if not token: print(f" ⚠️ 需要登录才能获取下载链接") result['need_login'] = True continue # 🔥 使用Android平台API获取下载链接 print(f" 🤖 使用Android平台API...") download_url = get_download_url_android(file_info, token) if download_url: result['data'].append({ "Name": file_name, "Size": file_info.get('Size', 0), "DownloadURL": download_url }) print(f" ✓ 成功获取直链\n") else: print(f" ✗ 获取失败\n") return result def format_size(size_bytes): """格式化文件大小""" for unit in ['B', 'KB', 'MB', 'GB', 'TB']: if size_bytes < 1024.0: return f"{size_bytes:.2f} {unit}" size_bytes /= 1024.0 return f"{size_bytes:.2f} PB" def main(): """主程序入口""" if len(sys.argv) < 2: print("=" * 80) print(" 123盘直链解析工具 v3.0") print("=" * 80) print("\n📖 使用方法:") print(" python 123.py <分享链接> [选项]") print("\n⚙️ 选项:") print(" --pwd <密码> 分享密码(如果有)") print(" --user <账号> 123盘账号") print(" --pass <密码> 123盘密码") print("\n💡 示例:") print(' # 需要登录的分享(推荐)') print(' python 123.py "https://www.123pan.com/s/xxxxx" --user "账号" --pass "密码"') print() print(' # 有分享密码') print(' python 123.py "https://www.123pan.com/s/xxxxx" --pwd "分享密码" --user "账号" --pass "密码"') print("\n✨ 特性:") print(" • 使用Android平台API(完全绕过限制)") print(" • 使用123盘加密算法(encode123)") print(" • 支持账号密码登录") print(" • 无地区限制,无流量限制") print("=" * 80) sys.exit(1) link = sys.argv[1] password = '' username = '' user_password = '' # 解析参数 i = 2 while i < len(sys.argv): if sys.argv[i] == '--pwd' and i + 1 < len(sys.argv): password = sys.argv[i + 1] i += 2 elif sys.argv[i] == '--user' and i + 1 < len(sys.argv): username = sys.argv[i + 1] i += 2 elif sys.argv[i] == '--pass' and i + 1 < len(sys.argv): user_password = sys.argv[i + 1] i += 2 else: i += 1 print("\n" + "=" * 80) print(" 开始解析分享链接") print("=" * 80) print(f"🔗 链接: {link}") if password: print(f"🔐 分享密码: {password}") if username: print(f"👤 登录账号: {username}") print("=" * 80) print() result = start(link, password, username, user_password) if result['code'] != 200: print(f"\n❌ 错误: {result['message']}") sys.exit(1) if not result['data']: print("\n⚠️ 没有成功获取到任何文件的直链") if result.get('need_login'): print("\n🔒 该分享需要登录才能下载") print("\n请使用以下命令:") print(f' python 123.py "{link}" --user "你的账号" --pass "你的密码"') sys.exit(1) print("\n" + "=" * 80) print(" ✅ 解析成功!") print("=" * 80) for idx, file in enumerate(result['data'], 1): print(f"\n📄 文件 {idx}:") print(f" 名称: {file['Name']}") print(f" 大小: {format_size(file['Size'])} ({file['Size']:,} 字节)") print(f" 直链: {file['DownloadURL']}") print("-" * 80) print("\n💾 下载方法:") print("\n 使用curl命令:") for file in result['data']: safe_name = file['Name'].replace('"', '\\"') print(f' curl -L -o "{safe_name}" "{file["DownloadURL"]}"') print("\n 使用aria2c命令(推荐,多线程):") for file in result['data']: safe_name = file['Name'].replace('"', '\\"') print(f' aria2c -x 16 -s 16 -o "{safe_name}" "{file["DownloadURL"]}"') print("\n💡 提示:") print(" • 使用Android平台API,无地区限制") print(" • 直链有效期通常为几小时") print(" • 推荐使用 aria2c 下载(速度最快)") print() if __name__ == "__main__": main()