├── 123pan.txt ├── README.md ├── android.py ├── requirements.txt ├── sign_py.py └── web.py /123pan.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 123云盘 下载工具 2 | 3 | ## 项目介绍 4 | 5 | 123Pan 下载工具是一个使用 Python 编写的脚本,通过模拟安卓客户端协议来绕过 123Pan 的下载流量限制。该工具可以帮助用户在 Windows 系统上方便地下载 123Pan 上的文件,并提供了多种操作功能,如列出文件、下载文件、上传文件、分享文件等。 6 | 7 | ## 功能特点 8 | 9 | - **登录**:使用用户名和密码登录 123Pan 账号。 10 | - **列出文件**:显示当前目录下的所有文件和文件夹。 11 | - **下载文件**:通过模拟安卓客户端协议下载文件,绕过流量限制。 12 | - **上传文件**:将本地文件上传到 123Pan。 13 | - **分享文件**:生成文件分享链接。 14 | - **删除文件**:删除指定的文件或文件夹。 15 | - **创建文件夹**:在当前目录下创建新文件夹。 16 | 17 | ## 使用方法 18 | 19 | ### 环境准备 20 | 21 | 1. 确保已安装 Python 3.x。 22 | 23 | 2. 安装所需的 Python 库: 24 | 25 | ```bash 26 | pip install -r requirements.txt 27 | ``` 28 | 29 | ### 配置文件 30 | 31 | 在首次运行脚本时,会自动生成一个 `123pan.txt` 文件,用于保存用户的登录信息。请确保该文件与脚本在同一目录下。 32 | 33 | ### 运行脚本 34 | 35 | ``` 36 | 可以直接下载release的可执行文件运行或运行python脚本。 37 | ``` 38 | 39 | ``` 40 | 脚本运行: 41 | ``` 42 | 43 | 1. 克隆或下载本项目代码到本地。 44 | 45 | 2. 打开终端或命令提示符,进入项目目录。 46 | 47 | 3. 运行脚本: 48 | 49 | 使用安卓客户端协议 50 | 51 | ```bash 52 | python android.py 53 | ``` 54 | 55 | ``` 56 | 使用web协议 57 | ``` 58 | 59 | ```bash 60 | python web.py 61 | ``` 62 | 63 | ### 命令 64 | 65 | - **登录**:`log` 66 | - **列出文件**:`ls` 67 | - **刷新目录**:`re` 68 | - **下载文件**:`download <文件编号>` 69 | 70 | Android下可以直接下载文件夹 71 | - **获取下载链接** `link <文件编号>` 72 | - **分享文件**:`share` 73 | - **删除文件**:`delete <文件编号>` 74 | - **创建文件夹**:`mkdir <文件夹名称>` 75 | - **切换目录**:`cd <目录编号>` 或 `cd ..` 返回上一级目录 76 | - **上传文件**:`upload`,然后输入文件路径 77 | - **直接输入数字**:进入文件夹,或是下载文件 78 | - **退出**:`exit` 79 | 80 | ### 示例 81 | 82 | 1. 登录: 83 | 84 | ```bash 85 | > log 86 | ``` 87 | 88 | 2. 列出文件: 89 | 90 | ```bash 91 | > ls↵ 92 | ``` 93 | 94 | 3. 下载文件: 95 | 96 | ```bash 97 | > download 1↵ 98 | ``` 99 | 100 | 或直接输入文件编号: 101 | 102 | ```bash 103 | >54↵ 104 | 1.20.0.01_v8a.apk 193.53M 105 | press 1 to download now: 1↵ 106 | 1.20.0.01_v8a.apk 193.53M 107 | [██████████████████████████████████████████████████] 100% 108 | ok 109 | ``` 110 | 111 | 下载文件夹(android): 112 | 113 | ```bash 114 | >download 2 115 | test 116 | 将打包下载文件夹,输入1开始下载:1 117 | 打包下载 118 | 文件 test.zip 已存在,是否要覆盖? 119 | 输入1覆盖,2取消:1 120 | test.zip 1.78K 121 | [██████████████████████████████████████████████████] 100% 122 | ok 123 | ``` 124 | 125 | 4. 获取下载链接 126 | 127 | ```bash 128 | >link 32 129 | https://1-180-24-9.pd1.cjjd19.com:30443/(A Long Link) 130 | ``` 131 | 132 | 获取下载链接后可以使用IDM下载 133 | 134 | 5. 分享文件: 135 | 136 | ```bash 137 | >share↵ 138 | 分享文件的编号:58↵ 139 | ['Something.jpg'] 140 | 输入1添加文件,0发起分享,其他取消0↵ 141 | 提取码,不设留空:↵ 142 | ok 143 | 分享链接: 144 | https://www.123pan.com/s/(someting)提取码:(something) 145 | ``` 146 | 147 | 6. 删除文件: 148 | 149 | ```bash 150 | > delete 1 151 | ``` 152 | 153 | 7. 创建文件夹: 154 | 155 | ```bash 156 | > mkdir 新建文件夹 157 | ``` 158 | 159 | 8. 上传文件: 160 | 161 | ```bash 162 | >upload 163 | 请输入文件路径:C:\(some path)\config 164 | 文件名: config 165 | 检测到1个同名文件,输入1覆盖,2保留两者,0取消:2 166 | 上传文件的fileId: (someThing) 167 | 已上传:100.0% 168 | 处理中 169 | 上传成功 170 | ``` 171 | 172 | ## 注意事项 173 | 174 | - 请确保在使用过程中网络连接正常。 175 | - 由于使用了模拟安卓客户端协议,可能会有一定的风险,请谨慎使用。 176 | - 本工具仅供学习和研究使用,请勿用于非法用途。 177 | -------------------------------------------------------------------------------- /android.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import os 4 | import re 5 | import time 6 | 7 | import uuid 8 | import requests 9 | 10 | 11 | 12 | class Pan123: 13 | def __init__( 14 | self, 15 | readfile=True, 16 | user_name="", 17 | pass_word="", 18 | authorization="", 19 | input_pwd=True, 20 | ): 21 | self.cookies = None 22 | self.recycle_list = None 23 | self.list = None 24 | if readfile: 25 | self.read_ini(user_name, pass_word, input_pwd, authorization) 26 | else: 27 | if user_name == "" or pass_word == "": 28 | print("读取已禁用,用户名或密码为空") 29 | if input_pwd: 30 | user_name = input("请输入用户名:") 31 | pass_word = input("请输入密码:") 32 | else: 33 | raise Exception("用户名或密码为空:读取禁用时,userName和passWord不能为空") 34 | self.user_name = user_name 35 | self.password = pass_word 36 | self.authorization = authorization 37 | self.header_logined = { 38 | "user-agent": "123pan/v2.4.0(Android_7.1.2;Xiaomi)", 39 | "authorization": self.authorization, 40 | "accept-encoding": "gzip", 41 | # "authorization": "", 42 | "content-type": "application/json", 43 | "osversion": "Android_7.1.2", 44 | "loginuuid": str(uuid.uuid4().hex), 45 | "platform": "android", 46 | "devicetype": "M2101K9C", 47 | "x-channel": "1004", 48 | "devicename": "Xiaomi", 49 | # "Content-Length": "65", 50 | "host": "www.123pan.com", 51 | "app-version": "61", 52 | "x-app-version": "2.4.0" 53 | } 54 | self.parent_file_id = 0 # 路径,文件夹的id,0为根目录 55 | self.parent_file_list = [0] 56 | res_code_getdir = self.get_dir() 57 | if res_code_getdir != 0: 58 | self.login() 59 | self.get_dir() 60 | 61 | def login(self): 62 | data = {"type": 1, "passport": self.user_name, "password": self.password} 63 | # sign = getSign("/b/api/user/sign_in") 64 | login_res = requests.post( 65 | "https://www.123pan.com/b/api/user/sign_in", 66 | headers=self.header_logined, 67 | data=data, 68 | # params={sign[0]: sign[1]}, timeout=10, 69 | # verify=False 70 | ) 71 | 72 | res_sign = login_res.json() 73 | # print("登录结果:", res_sign) 74 | # print("登录结果header:", login_res.headers) 75 | res_code_login = res_sign["code"] 76 | if res_code_login != 200: 77 | print("code = 1 Error:" + str(res_code_login)) 78 | print(res_sign["message"]) 79 | return res_code_login 80 | set_cookies = login_res.headers["Set-Cookie"] 81 | set_cookies_list = {} 82 | 83 | for cookie in set_cookies.split(';'): 84 | if '=' in cookie: 85 | key, value = cookie.strip().split('=', 1) 86 | set_cookies_list[key] = value 87 | else: 88 | set_cookies_list[cookie.strip()] = None 89 | 90 | self.cookies = set_cookies_list 91 | 92 | token = res_sign["data"]["token"] 93 | self.authorization = "Bearer " + token 94 | self.header_logined["authorization"] = self.authorization 95 | # ret['cookie'] = cookie 96 | self.save_file() 97 | return res_code_login 98 | 99 | def save_file(self): 100 | with open("123pan.txt", "w", encoding="utf_8") as f: 101 | save_list = { 102 | "userName": self.user_name, 103 | "passWord": self.password, 104 | "authorization": self.authorization, 105 | } 106 | 107 | f.write(json.dumps(save_list)) 108 | print("账号已保存") 109 | 110 | def get_dir(self): 111 | res_code_getdir = 0 112 | page = 1 113 | lists = [] 114 | lenth_now = 0 115 | total = -1 116 | while lenth_now < total or total == -1: 117 | base_url = "https://www.123pan.com/b/api/file/list/new" 118 | # print(self.headerLogined) 119 | # sign = getSign("/b/api/file/list/new") 120 | # print(sign) 121 | params = { 122 | # sign[0]: sign[1], 123 | "driveId": 0, 124 | "limit": 100, 125 | "next": 0, 126 | "orderBy": "file_id", 127 | "orderDirection": "desc", 128 | "parentFileId": str(self.parent_file_id), 129 | "trashed": False, 130 | "SearchData": "", 131 | "Page": str(page), 132 | "OnlyLookAbnormalFile": 0, 133 | } 134 | try: 135 | a = requests.get(base_url, headers=self.header_logined, params=params, timeout=10) # , verify=False) 136 | except: 137 | print("连接失败") 138 | return -1 139 | # print(a.text) 140 | # print(a.headers) 141 | text = a.json() 142 | res_code_getdir = text["code"] 143 | if res_code_getdir != 0: 144 | #print(a.text) 145 | #print(a.headers) 146 | print("code = 2 Error:" + str(res_code_getdir)) 147 | return res_code_getdir 148 | lists_page = text["data"]["InfoList"] 149 | lists += lists_page 150 | total = text["data"]["Total"] 151 | lenth_now += len(lists_page) 152 | page += 1 153 | file_num = 0 154 | for i in lists: 155 | i["FileNum"] = file_num 156 | file_num += 1 157 | 158 | self.list = lists 159 | return res_code_getdir 160 | 161 | def show(self): 162 | print("--------------------") 163 | for i in self.list: 164 | file_size = i["Size"] 165 | if file_size > 1048576: 166 | download_size_print = str(round(file_size / 1048576, 2)) + "M" 167 | else: 168 | download_size_print = str(round(file_size / 1024, 2)) + "K" 169 | 170 | if i["Type"] == 0: 171 | print( 172 | "\033[33m" + "编号:", 173 | self.list.index(i) + 1, 174 | "\033[0m \t\t" + download_size_print + "\t\t\033[36m", 175 | i["FileName"], 176 | "\033[0m", 177 | ) 178 | elif i["Type"] == 1: 179 | print( 180 | "\033[35m" + "编号:", 181 | self.list.index(i) + 1, 182 | " \t\t\033[36m", 183 | i["FileName"], 184 | "\033[0m", 185 | ) 186 | 187 | print("--------------------") 188 | 189 | # fileNumber 从0开始,0为第一个文件,传入时需要减一 !!! 190 | def link(self, file_number, showlink=True): 191 | file_detail = self.list[file_number] 192 | type_detail = file_detail["Type"] 193 | if type_detail == 1: 194 | down_request_url = "https://www.123pan.com/a/api/file/batch_download_info" 195 | down_request_data = {"fileIdList": [{"fileId": int(file_detail["FileId"])}]} 196 | 197 | else: 198 | down_request_url = "https://www.123pan.com/a/api/file/download_info" 199 | down_request_data = { 200 | "driveId": 0, 201 | "etag": file_detail["Etag"], 202 | "fileId": file_detail["FileId"], 203 | "s3keyFlag": file_detail["S3KeyFlag"], 204 | "type": file_detail["Type"], 205 | "fileName": file_detail["FileName"], 206 | "size": file_detail["Size"], 207 | } 208 | # print(down_request_data) 209 | 210 | # sign = getSign("/a/api/file/download_info") 211 | 212 | link_res = requests.post( 213 | down_request_url, 214 | headers=self.header_logined, 215 | # params={sign[0]: sign[1]}, 216 | data=json.dumps(down_request_data), 217 | timeout=10 218 | ) 219 | # print(linkRes.text) 220 | res_code_download = link_res.json()["code"] 221 | if res_code_download != 0: 222 | print("code = 3 Error:" + str(res_code_download)) 223 | # print(linkRes.json()) 224 | return res_code_download 225 | down_load_url = link_res.json()["data"]["DownloadUrl"] 226 | next_to_get = requests.get(down_load_url, timeout=10, allow_redirects=False).text 227 | url_pattern = re.compile(r"href='(https?://[^']+)'") 228 | redirect_url = url_pattern.findall(next_to_get)[0] 229 | if showlink: 230 | print(redirect_url) 231 | 232 | return redirect_url 233 | 234 | def download(self, file_number,download_path="download/"): 235 | file_detail = self.list[file_number] 236 | if file_detail["Type"] == 1: 237 | print("打包下载") 238 | file_name = file_detail["FileName"] + ".zip" 239 | else: 240 | file_name = file_detail["FileName"] # 文件名 241 | down_load_url = self.link(file_number, showlink=False) 242 | 243 | if os.path.exists(download_path+file_name): 244 | print("文件 " + file_name + " 已存在,是否要覆盖?") 245 | sure_download = input("输入1覆盖,2取消:") 246 | if sure_download != "1": 247 | return 248 | 249 | if not os.path.exists(download_path): 250 | print("文件夹不存在,创建文件夹") 251 | os.makedirs(download_path) 252 | down = requests.get(down_load_url, stream=True, timeout=10) 253 | 254 | file_size = int(down.headers["Content-Length"]) # 文件大小 255 | content_size = int(file_size) # 文件总大小 256 | data_count = 0 # 当前已传输的大小 257 | if file_size > 1048576: 258 | size_print_download = str(round(file_size / 1048576, 2)) + "M" 259 | else: 260 | size_print_download = str(round(file_size / 1024, 2)) + "K" 261 | print(file_name + " " + size_print_download) 262 | time1 = time.time() 263 | time_temp = time1 264 | data_count_temp = 0 265 | with open(download_path+file_name, "wb") as f: 266 | for i in down.iter_content(1024): 267 | f.write(i) 268 | done_block = int((data_count / content_size) * 50) 269 | data_count = data_count + len(i) 270 | # 实时进度条进度 271 | now_jd = (data_count / content_size) * 100 272 | # %% 表示% 273 | # 测速 274 | time1 = time.time() 275 | pass_time = time1 - time_temp 276 | if pass_time > 1: 277 | time_temp = time1 278 | pass_data = int(data_count) - int(data_count_temp) 279 | data_count_temp = data_count 280 | speed = pass_data / int(pass_time) 281 | speed_m = speed / 1048576 282 | if speed_m > 1: 283 | speed_print = str(round(speed_m, 2)) + "M/S" 284 | else: 285 | speed_print = str(round(speed_m * 1024, 2)) + "K/S" 286 | print( 287 | "\r [%s%s] %d%% %s" 288 | % ( 289 | done_block * "█", 290 | " " * (50 - 1 - done_block), 291 | now_jd, 292 | speed_print, 293 | ), 294 | end="", 295 | ) 296 | elif data_count == content_size: 297 | print("\r [%s%s] %d%% %s" % (50 * "█", "", 100, ""), end="") 298 | print("\nok") 299 | 300 | def recycle(self): 301 | recycle_id = 0 302 | url = ( 303 | "https://www.123pan.com/a/api/file/list/new?driveId=0&limit=100&next=0" 304 | "&orderBy=fileId&orderDirection=desc&parentFileId=" 305 | + str(recycle_id) 306 | + "&trashed=true&&Page=1" 307 | ) 308 | recycle_res = requests.get(url, headers=self.header_logined, timeout=10) 309 | json_recycle = recycle_res.json() 310 | recycle_list = json_recycle["data"]["InfoList"] 311 | self.recycle_list = recycle_list 312 | 313 | # fileNumber 从0开始,0为第一个文件,传入时需要减一 !!! 314 | def delete_file(self, file, by_num=True, operation=True): 315 | # operation = 'true' 删除 , operation = 'false' 恢复 316 | if by_num: 317 | print(file) 318 | if not str(file).isdigit(): 319 | print("请输入数字") 320 | return -1 321 | if 0 <= file < len(self.list): 322 | file_detail = self.list[file] 323 | else: 324 | print("不在合理范围内") 325 | return 326 | else: 327 | if file in self.list: 328 | file_detail = file 329 | else: 330 | print("文件不存在") 331 | return 332 | data_delete = { 333 | "driveId": 0, 334 | "fileTrashInfoList": file_detail, 335 | "operation": operation, 336 | } 337 | delete_res = requests.post( 338 | "https://www.123pan.com/a/api/file/trash", 339 | data=json.dumps(data_delete), 340 | headers=self.header_logined, 341 | timeout=10 342 | ) 343 | dele_json = delete_res.json() 344 | print(dele_json) 345 | message = dele_json["message"] 346 | print(message) 347 | 348 | def share(self): 349 | file_id_list = "" 350 | share_name_list = [] 351 | add = "1" 352 | while str(add) == "1": 353 | share_num = input("分享文件的编号:") 354 | num_test2 = share_num.isdigit() 355 | if num_test2: 356 | share_num = int(share_num) 357 | if 0 < share_num < len(self.list) + 1: 358 | share_id = self.list[int(share_num) - 1]["FileId"] 359 | share_name = self.list[int(share_num) - 1]["FileName"] 360 | share_name_list.append(share_name) 361 | print(share_name_list) 362 | file_id_list = file_id_list + str(share_id) + "," 363 | add = input("输入1添加文件,0发起分享,其他取消") 364 | else: 365 | print("请输入数字,,") 366 | add = "1" 367 | if str(add) == "0": 368 | share_pwd = input("提取码,不设留空:") 369 | file_id_list = file_id_list.strip(",") 370 | data = { 371 | "driveId": 0, 372 | "expiration": "2099-12-12T08:00:00+08:00", 373 | "fileIdList": file_id_list, 374 | "shareName": "My Share", 375 | "sharePwd": share_pwd, 376 | "event": "shareCreate" 377 | } 378 | share_res = requests.post( 379 | "https://www.123pan.com/a/api/share/create", 380 | headers=self.header_logined, 381 | data=json.dumps(data), 382 | timeout=10 383 | ) 384 | share_res_json = share_res.json() 385 | if share_res_json["code"] != 0: 386 | print(share_res_json["message"]) 387 | print("分享失败") 388 | return 389 | message = share_res_json["message"] 390 | print(message) 391 | share_key = share_res_json["data"]["ShareKey"] 392 | share_url = "https://www.123pan.com/s/" + share_key 393 | print("分享链接:\n" + share_url + "提取码:" + share_pwd) 394 | else: 395 | print("退出分享") 396 | 397 | def up_load(self, file_path): 398 | file_path = file_path.replace('"', "") 399 | file_path = file_path.replace("\\", "/") 400 | file_name = file_path.split("/")[-1] 401 | print("文件名:", file_name) 402 | if not os.path.exists(file_path): 403 | print("文件不存在,请检查路径是否正确") 404 | return 405 | if os.path.isdir(file_path): 406 | print("暂不支持文件夹上传") 407 | return 408 | fsize = os.path.getsize(file_path) 409 | with open(file_path, "rb") as f: 410 | md5 = hashlib.md5() 411 | while True: 412 | data = f.read(64 * 1024) 413 | if not data: 414 | break 415 | md5.update(data) 416 | readable_hash = md5.hexdigest() 417 | 418 | list_up_request = { 419 | "driveId": 0, 420 | "etag": readable_hash, 421 | "fileName": file_name, 422 | "parentFileId": self.parent_file_id, 423 | "size": fsize, 424 | "type": 0, 425 | "duplicate": 0, 426 | } 427 | 428 | # sign = getSign("/b/api/file/upload_request") 429 | up_res = requests.post( 430 | "https://www.123pan.com/b/api/file/upload_request", 431 | headers=self.header_logined, 432 | # params={sign[0]: sign[1]}, 433 | data=list_up_request, 434 | timeout=10 435 | ) 436 | up_res_json = up_res.json() 437 | res_code_up = up_res_json["code"] 438 | if res_code_up == 5060: 439 | sure_upload = input("检测到1个同名文件,输入1覆盖,2保留两者,0取消:") 440 | if sure_upload == "1": 441 | list_up_request["duplicate"] = 1 442 | 443 | elif sure_upload == "2": 444 | list_up_request["duplicate"] = 2 445 | else: 446 | print("取消上传") 447 | return 448 | # sign = getSign("/b/api/file/upload_request") 449 | up_res = requests.post( 450 | "https://www.123pan.com/b/api/file/upload_request", 451 | headers=self.header_logined, 452 | # params={sign[0]: sign[1]}, 453 | data=json.dumps(list_up_request), 454 | timeout=10 455 | ) 456 | up_res_json = up_res.json() 457 | res_code_up = up_res_json["code"] 458 | if res_code_up == 0: 459 | # print(upResJson) 460 | # print("上传请求成功") 461 | reuse = up_res_json["data"]["Reuse"] 462 | if reuse: 463 | print("上传成功,文件已MD5复用") 464 | return 465 | else: 466 | print(up_res_json) 467 | print("上传请求失败") 468 | return 469 | 470 | bucket = up_res_json["data"]["Bucket"] 471 | storage_node = up_res_json["data"]["StorageNode"] 472 | upload_key = up_res_json["data"]["Key"] 473 | upload_id = up_res_json["data"]["UploadId"] 474 | up_file_id = up_res_json["data"]["FileId"] # 上传文件的fileId,完成上传后需要用到 475 | print("上传文件的fileId:", up_file_id) 476 | 477 | # 获取已将上传的分块 478 | start_data = { 479 | "bucket": bucket, 480 | "key": upload_key, 481 | "uploadId": upload_id, 482 | "storageNode": storage_node, 483 | } 484 | start_res = requests.post( 485 | "https://www.123pan.com/b/api/file/s3_list_upload_parts", 486 | headers=self.header_logined, 487 | data=json.dumps(start_data), 488 | timeout=10 489 | ) 490 | start_res_json = start_res.json() 491 | res_code_up = start_res_json["code"] 492 | if res_code_up == 0: 493 | # print(startResJson) 494 | pass 495 | else: 496 | print(start_data) 497 | print(start_res_json) 498 | 499 | print("获取传输列表失败") 500 | return 501 | 502 | # 分块,每一块取一次链接,依次上传 503 | block_size = 5242880 504 | with open(file_path, "rb") as f: 505 | part_number_start = 1 506 | put_size = 0 507 | while True: 508 | data = f.read(block_size) 509 | 510 | precent = round(put_size / fsize, 2) 511 | print("\r已上传:" + str(precent * 100) + "%", end="") 512 | put_size = put_size + len(data) 513 | 514 | if not data: 515 | break 516 | get_link_data = { 517 | "bucket": bucket, 518 | "key": upload_key, 519 | "partNumberEnd": part_number_start + 1, 520 | "partNumberStart": part_number_start, 521 | "uploadId": upload_id, 522 | "StorageNode": storage_node, 523 | } 524 | 525 | get_link_url = ( 526 | "https://www.123pan.com/b/api/file/s3_repare_upload_parts_batch" 527 | ) 528 | get_link_res = requests.post( 529 | get_link_url, 530 | headers=self.header_logined, 531 | data=json.dumps(get_link_data), 532 | timeout=10 533 | ) 534 | get_link_res_json = get_link_res.json() 535 | res_code_up = get_link_res_json["code"] 536 | if res_code_up == 0: 537 | # print("获取链接成功") 538 | pass 539 | else: 540 | print("获取链接失败") 541 | # print(getLinkResJson) 542 | return 543 | # print(getLinkResJson) 544 | upload_url = get_link_res_json["data"]["presignedUrls"][ 545 | str(part_number_start) 546 | ] 547 | # print("上传链接",uploadUrl) 548 | requests.put(upload_url, data=data, timeout=10) 549 | # print("put") 550 | 551 | part_number_start = part_number_start + 1 552 | 553 | print("\n处理中") 554 | # 完成标志 555 | # 1.获取已上传的块 556 | uploaded_list_url = "https://www.123pan.com/b/api/file/s3_list_upload_parts" 557 | uploaded_comp_data = { 558 | "bucket": bucket, 559 | "key": upload_key, 560 | "uploadId": upload_id, 561 | "storageNode": storage_node, 562 | } 563 | # print(uploadedCompData) 564 | requests.post( 565 | uploaded_list_url, 566 | headers=self.header_logined, 567 | data=json.dumps(uploaded_comp_data), 568 | timeout=10 569 | ) 570 | compmultipart_up_url = ( 571 | "https://www.123pan.com/b/api/file/s3_complete_multipart_upload" 572 | ) 573 | requests.post( 574 | compmultipart_up_url, 575 | headers=self.header_logined, 576 | data=json.dumps(uploaded_comp_data), 577 | timeout=10 578 | ) 579 | 580 | # 3.报告完成上传,关闭upload session 581 | if fsize > 64 * 1024 * 1024: 582 | time.sleep(3) 583 | close_up_session_url = "https://www.123pan.com/b/api/file/upload_complete" 584 | close_up_session_data = {"fileId": up_file_id} 585 | # print(closeUpSessionData) 586 | close_up_session_res = requests.post( 587 | close_up_session_url, 588 | headers=self.header_logined, 589 | data=json.dumps(close_up_session_data), 590 | timeout=10 591 | ) 592 | close_res_json = close_up_session_res.json() 593 | # print(closeResJson) 594 | res_code_up = close_res_json["code"] 595 | if res_code_up == 0: 596 | print("上传成功") 597 | else: 598 | print("上传失败") 599 | print(close_res_json) 600 | return 601 | 602 | # dirId 就是 fileNumber,从0开始,0为第一个文件,传入时需要减一 !!!(好像文件夹都排在前面) 603 | def cd(self, dir_num): 604 | if not dir_num.isdigit(): 605 | if dir_num == "..": 606 | if len(self.parent_file_list) > 1: 607 | self.parent_file_list.pop() 608 | self.parent_file_id = self.parent_file_list[-1] 609 | self.get_dir() 610 | self.show() 611 | else: 612 | print("已经是根目录") 613 | return 614 | if dir_num == "/": 615 | self.parent_file_id = 0 616 | self.parent_file_list = [0] 617 | self.get_dir() 618 | self.show() 619 | return 620 | print("输入错误") 621 | return 622 | dir_num = int(dir_num) - 1 623 | if dir_num >= (len(self.list) - 1) or dir_num < 0: 624 | print("输入错误") 625 | return 626 | if self.list[dir_num]["Type"] != 1: 627 | print("不是文件夹") 628 | return 629 | self.parent_file_id = self.list[dir_num]["FileId"] 630 | self.parent_file_list.append(self.parent_file_id) 631 | self.get_dir() 632 | self.show() 633 | 634 | def cdById(self, file_id): 635 | self.parent_file_id = file_id 636 | self.parent_file_list.append(self.parent_file_id) 637 | self.get_dir() 638 | self.get_dir() 639 | self.show() 640 | 641 | def read_ini( 642 | self, 643 | user_name, 644 | pass_word, 645 | input_pwd, 646 | authorization="", 647 | ): 648 | try: 649 | with open("123pan.txt", "r", encoding="utf-8") as f: 650 | text = f.read() 651 | text = json.loads(text) 652 | user_name = text["userName"] 653 | pass_word = text["passWord"] 654 | authorization = text["authorization"] 655 | 656 | except: # FileNotFoundError or json.decoder.JSONDecodeError: 657 | print("获取配置失败,重新输入") 658 | 659 | if user_name == "" or pass_word == "": 660 | if input_pwd: 661 | user_name = input("userName:") 662 | pass_word = input("passWord:") 663 | authorization = "" 664 | else: 665 | raise Exception("禁止输入模式下,没有账号或密码") 666 | 667 | self.user_name = user_name 668 | self.password = pass_word 669 | self.authorization = authorization 670 | 671 | def mkdir(self, dirname, remakedir=False): 672 | if not remakedir: 673 | for i in self.list: 674 | if i["FileName"] == dirname: 675 | print("文件夹已存在") 676 | return i["FileId"] 677 | 678 | url = "https://www.123pan.com/a/api/file/upload_request" 679 | data_mk = { 680 | "driveId": 0, 681 | "etag": "", 682 | "fileName": dirname, 683 | "parentFileId": self.parent_file_id, 684 | "size": 0, 685 | "type": 1, 686 | "duplicate": 1, 687 | "NotReuse": True, 688 | "event": "newCreateFolder", 689 | "operateType": 1, 690 | } 691 | # sign = getSign("/a/api/file/upload_request") 692 | res_mk = requests.post( 693 | url, 694 | headers=self.header_logined, 695 | data=json.dumps(data_mk), 696 | # params={sign[0]: sign[1]}, 697 | timeout=10 698 | ) 699 | try: 700 | res_json = res_mk.json() 701 | # print(res_json) 702 | except json.decoder.JSONDecodeError: 703 | print("创建失败") 704 | print(res_mk.text) 705 | return 706 | code_mkdir = res_json["code"] 707 | 708 | if code_mkdir == 0: 709 | print("创建成功: ", res_json["data"]["FileId"]) 710 | self.get_dir() 711 | return res_json["data"]["Info"]["FileId"] 712 | print(res_json) 713 | print("创建失败") 714 | return 715 | 716 | 717 | if __name__ == "__main__": 718 | pan = Pan123(readfile=True, input_pwd=True) 719 | pan.show() 720 | while True: 721 | command = input("\033[91m >\033[0m") 722 | if command == "ls": 723 | pan.show() 724 | if command == "re": 725 | code = pan.get_dir() 726 | if code == 0: 727 | print("刷新目录成功") 728 | pan.show() 729 | if command.isdigit(): 730 | if int(command) > len(pan.list) or int(command) < 1: 731 | print("输入错误") 732 | continue 733 | if pan.list[int(command) - 1]["Type"] == 1: 734 | pan.cdById(pan.list[int(command) - 1]["FileId"]) 735 | else: 736 | size = pan.list[int(command) - 1]["Size"] 737 | if size > 1048576: 738 | size_print_show = str(round(size / 1048576, 2)) + "M" 739 | else: 740 | size_print_show = str(round(size / 1024, 2)) + "K" 741 | # print(pan.list[int(command) - 1]) 742 | name = pan.list[int(command) - 1]["FileName"] 743 | print(name + " " + size_print_show) 744 | print("输入1开始下载: ", end="") 745 | sure = input() 746 | if sure == "1": 747 | pan.download(int(command) - 1) 748 | elif command[0:9] == "download ": 749 | if command[9:].isdigit(): 750 | if int(command[9:]) > len(pan.list) or int(command[9:]) < 1: 751 | print("输入错误") 752 | continue 753 | if pan.list[int(command[9:]) - 1]["Type"] == 1: 754 | print(pan.list[int(command[9:]) - 1]["FileName"]) 755 | print("将打包下载文件夹,输入1开始下载:", end="") 756 | sure = input() 757 | if sure != "1": 758 | continue 759 | pan.download(int(command[9:]) - 1) 760 | else: 761 | print("输入错误") 762 | elif command == "exit": 763 | break 764 | elif command == "log": 765 | pan.login() 766 | pan.get_dir() 767 | pan.show() 768 | 769 | elif command[0:5] == "link ": 770 | if command[5:].isdigit(): 771 | if int(command[5:]) > len(pan.list) or int(command[5:]) < 1: 772 | print("输入错误") 773 | continue 774 | pan.link(int(command[5:]) - 1) 775 | else: 776 | print("输入错误") 777 | elif command == "upload": 778 | filepath = input("请输入文件路径:") 779 | pan.up_load(filepath) 780 | pan.get_dir() 781 | pan.show() 782 | elif command == "share": 783 | pan.share() 784 | elif command[0:6] == "delete": 785 | if command == "delete": 786 | print("请输入要删除的文件编号:", end="") 787 | fileNumber = input() 788 | else: 789 | if command[6] == " ": 790 | fileNumber = command[7:] 791 | else: 792 | print("输入错误") 793 | continue 794 | if fileNumber == "": 795 | print("请输入要删除的文件编号:", end="") 796 | fileNumber = input() 797 | else: 798 | fileNumber = fileNumber[0:] 799 | if fileNumber.isdigit(): 800 | if int(fileNumber) > len(pan.list) or int(fileNumber) < 1: 801 | print("输入错误") 802 | continue 803 | pan.delete_file(int(fileNumber) - 1) 804 | pan.get_dir() 805 | pan.show() 806 | else: 807 | print("输入错误") 808 | 809 | elif command[:3] == "cd ": 810 | path = command[3:] 811 | pan.cd(path) 812 | elif command[0:5] == "mkdir": 813 | if command == "mkdir": 814 | newPath = input("请输入目录名:") 815 | else: 816 | newPath = command[6:] 817 | if newPath == "": 818 | newPath = input("请输入目录名:") 819 | else: 820 | newPath = newPath[0:] 821 | print(pan.mkdir(newPath)) 822 | pan.get_dir() 823 | pan.show() 824 | 825 | elif command == "reload": 826 | pan.read_ini("", "", True) 827 | print("读取成功") 828 | pan.get_dir() 829 | pan.show() 830 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests~=2.31.0 -------------------------------------------------------------------------------- /sign_py.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | from datetime import datetime 4 | 5 | 6 | def getSign(e): 7 | def unsigned_right_shift(n, shift): 8 | return (n % 0x100000000) >> shift 9 | 10 | def simulate_js_overflow(js_int, n): 11 | # 转二进制 12 | if js_int < 0: 13 | js_int = -js_int 14 | js_int = str(bin(js_int))[2:] 15 | js_int = js_int.zfill(32) 16 | js_int = js_int.replace("0", "2") 17 | js_int = js_int.replace("1", "0") 18 | js_int = js_int.replace("2", "1") 19 | js_int = int(js_int, 2) + 1 20 | bin_int = str(bin(js_int))[2:].zfill(32) 21 | if n < 0: 22 | # 转补码 23 | n = -n 24 | n = str(bin(n))[2:] 25 | n = n.zfill(32) 26 | n = n.replace("0", "2") 27 | n = n.replace("1", "0") 28 | n = n.replace("2", "1") 29 | n = int(n, 2) + 1 30 | bin_n = str(bin(n))[2:].zfill(32) 31 | result = "" 32 | for i in range(0, len(bin_int)): 33 | temp = int(bin_n[i]) ^ int(bin_int[i]) 34 | result = result + str(temp) 35 | if result[0] == "1": 36 | # 取补码 37 | result = result.replace("0", "2") 38 | result = result.replace("1", "0") 39 | result = result.replace("2", "1") 40 | result = int(result, 2) + 1 41 | result = -result 42 | else: 43 | result = int(result, 2) 44 | return result 45 | 46 | def A(t): 47 | r = t.replace('\r\n', '\n') 48 | a = -1 49 | 50 | def generate_array(): 51 | t = [] 52 | for e in range(256): 53 | n = e 54 | for _ in range(8): 55 | if n & 1: # 如果 n 的最低位是 1 56 | # print("入口:n:", n) 57 | n = simulate_js_overflow(3988292384, unsigned_right_shift(n, 1)) 58 | else: 59 | n = unsigned_right_shift(n, 1) 60 | t.append(n) 61 | return t 62 | 63 | n = generate_array() 64 | # print(n) 65 | for i in range(len(r)): 66 | # print("a:", unsigned_right_shift(a, 8)) 67 | a = unsigned_right_shift(a, 8) ^ n[255 & (a ^ ord(r[i]))] 68 | # print("zz", a) 69 | return str((simulate_js_overflow(-1, a)) & 0xFFFFFFFF) 70 | 71 | def generate_timestamp(): 72 | return round((time.time() + datetime.now().astimezone().utcoffset().total_seconds() + 28800) / 1) 73 | 74 | def adjust_timestamp(o, timestamp): 75 | if timestamp: 76 | i = timestamp 77 | m = i 78 | if 20 <= abs(1000 * o - 1000 * int(m)) / 1000 / 60: 79 | return i 80 | return o 81 | 82 | def formatDate(t, e=None, n=8): 83 | t = int(t) # Use the original timestamp 84 | t = t - 480 * 60 85 | r = datetime.fromtimestamp(t + 3600 * n) # Convert to seconds and add 'n' hours 86 | data = { 87 | 'y': str(r.year), 88 | 'm': f"0{r.month}" if r.month < 10 else str(r.month), 89 | 'd': f"0{r.day}" if r.day < 10 else str(r.day), 90 | 'h': f"0{r.hour}" if r.hour < 10 else str(r.hour), 91 | 'f': f"0{r.minute}" if r.minute < 10 else str(r.minute) 92 | } 93 | return data 94 | 95 | def generate_signature(a, o, e, n, r): 96 | s = ["a", "d", "e", "f", "g", "h", "l", "m", "y", "i", "j", "n", "o", "p", "k", "q", "r", "s", "t", "u", "b", 97 | "c", "v", "w", "s", "z"] 98 | u = formatDate(o) 99 | h = u['y'] 100 | g = u['m'] 101 | l = u['d'] 102 | c = u['h'] 103 | u = u['f'] 104 | d = ''.join([h, g, l, c, u]) 105 | f = [s[int(p)] for p in d] 106 | h = A(''.join(f)) 107 | g = A(f"{o}|{a}|{e}|{n}|{r}|{h}") 108 | return [h, f"{o}-{a}-{g}"] 109 | 110 | a = str(random.randint(0, 9999999)) 111 | o = generate_timestamp() 112 | o = adjust_timestamp(o, timestamp=round(time.time())) 113 | 114 | n = "web" 115 | r = '3' 116 | return generate_signature(a, o, e, n, r) 117 | 118 | 119 | if __name__ == '__main__': 120 | e = '/b/api/file/list/new' 121 | print(getSign(e)) 122 | -------------------------------------------------------------------------------- /web.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | from sign_py import getSign 4 | import requests 5 | import hashlib 6 | import os 7 | import json 8 | import base64 9 | 10 | 11 | class Pan123: 12 | def __init__( 13 | self, 14 | readfile=True, 15 | user_name="", 16 | pass_word="", 17 | authorization="", 18 | input_pwd=True, 19 | ): 20 | self.recycle_list = None 21 | self.list = None 22 | if readfile: 23 | self.read_ini(user_name, pass_word, input_pwd, authorization) 24 | else: 25 | if user_name == "" or pass_word == "": 26 | print("读取已禁用,用户名或密码为空") 27 | if input_pwd: 28 | user_name = input("请输入用户名:") 29 | pass_word = input("请输入密码:") 30 | else: 31 | raise Exception("用户名或密码为空:读取禁用时,userName和passWord不能为空") 32 | self.user_name = user_name 33 | self.password = pass_word 34 | self.authorization = authorization 35 | self.header_only_usage = { 36 | "user-agent": "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/" 37 | "537.36 (KHTML, like Gecko) Chrome/109.0.0.0 " 38 | "Safari/537.36 Edg/109.0.1474.0", 39 | "app-version": "2", 40 | "platform": "web", 41 | } 42 | self.header_logined = { 43 | "Accept": "*/*", 44 | "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", 45 | "App-Version": "3", 46 | "Authorization": self.authorization, 47 | "Cache-Control": "no-cache", 48 | "Connection": "keep-alive", 49 | "LoginUuid": "z-uk_yT8HwR4raGX1gqGk", 50 | "Pragma": "no-cache", 51 | "Referer": "https://www.123pan.com/", 52 | "Sec-Fetch-Dest": "empty", 53 | "Sec-Fetch-Mode": "cors", 54 | "Sec-Fetch-Site": "same-origin", 55 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/" 56 | "537.36 (KHTML, like Gecko) Chrome/119.0.0.0 " 57 | "Safari/537.36 Edg/119.0.0.0", 58 | "platform": "web", 59 | "sec-ch-ua": "^\\^Microsoft", 60 | "sec-ch-ua-mobile": "?0", 61 | "sec-ch-ua-platform": "^\\^Windows^^", 62 | } 63 | self.parent_file_id = 0 # 路径,文件夹的id,0为根目录 64 | self.parent_file_list = [0] 65 | res_code_getdir = self.get_dir() 66 | if res_code_getdir != 0: 67 | self.login() 68 | self.get_dir() 69 | 70 | def login(self): 71 | data = {"remember": True, "passport": self.user_name, "password": self.password} 72 | sign = getSign("/b/api/user/sign_in") 73 | login_res = requests.post( 74 | "https://www.123pan.com/b/api/user/sign_in", 75 | headers=self.header_only_usage, 76 | data=data, 77 | params={sign[0]: sign[1]}, timeout=10 78 | ) 79 | res_sign = login_res.json() 80 | res_code_login = res_sign["code"] 81 | if res_code_login != 200: 82 | print("code = 1 Error:" + str(res_code_login)) 83 | print(res_sign["message"]) 84 | return res_code_login 85 | token = res_sign["data"]["token"] 86 | self.authorization = "Bearer " + token 87 | header_logined = { 88 | "Accept": "*/*", 89 | "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", 90 | "App-Version": "3", 91 | "Authorization": self.authorization, 92 | "Cache-Control": "no-cache", 93 | "Connection": "keep-alive", 94 | "LoginUuid": "z-uk_yT8HwR4raGX1gqGk", 95 | "Pragma": "no-cache", 96 | "Referer": "https://www.123pan.com/", 97 | "Sec-Fetch-Dest": "empty", 98 | "Sec-Fetch-Mode": "cors", 99 | "Sec-Fetch-Site": "same-origin", 100 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/" 101 | "537.36 (KHTML, like" 102 | " Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0", 103 | "platform": "web", 104 | "sec-ch-ua": "^\\^Microsoft", 105 | "sec-ch-ua-mobile": "?0", 106 | "sec-ch-ua-platform": "^\\^Windows^^", 107 | } 108 | self.header_logined = header_logined 109 | # ret['cookie'] = cookie 110 | self.save_file() 111 | return res_code_login 112 | 113 | def save_file(self): 114 | with open("123pan.txt", "w",encoding="utf_8") as f: 115 | save_list = { 116 | "userName": self.user_name, 117 | "passWord": self.password, 118 | "authorization": self.authorization, 119 | } 120 | 121 | f.write(json.dumps(save_list)) 122 | print("Save!") 123 | 124 | def get_dir(self): 125 | res_code_getdir = 0 126 | page = 1 127 | lists = [] 128 | lenth_now = 0 129 | total = -1 130 | while lenth_now < total or total == -1: 131 | base_url = "https://www.123pan.com/b/api/file/list/new" 132 | 133 | # print(self.headerLogined) 134 | sign = getSign("/b/api/file/list/new") 135 | print(sign) 136 | params = { 137 | sign[0]: sign[1], 138 | "driveId": 0, 139 | "limit": 100, 140 | "next": 0, 141 | "orderBy": "file_id", 142 | "orderDirection": "desc", 143 | "parentFileId": str(self.parent_file_id), 144 | "trashed": False, 145 | "SearchData": "", 146 | "Page": str(page), 147 | "OnlyLookAbnormalFile": 0, 148 | } 149 | 150 | a = requests.get(base_url, headers=self.header_logined, params=params, timeout=10) 151 | # print(a.text) 152 | # print(a.headers) 153 | text = a.json() 154 | res_code_getdir = text["code"] 155 | if res_code_getdir != 0: 156 | print(a.text) 157 | print(a.headers) 158 | print("code = 2 Error:" + str(res_code_getdir)) 159 | return res_code_getdir 160 | lists_page = text["data"]["InfoList"] 161 | lists += lists_page 162 | total = text["data"]["Total"] 163 | lenth_now += len(lists_page) 164 | page += 1 165 | file_num = 0 166 | for i in lists: 167 | i["FileNum"] = file_num 168 | file_num += 1 169 | 170 | self.list = lists 171 | return res_code_getdir 172 | 173 | def show(self): 174 | print("--------------------") 175 | for i in self.list: 176 | file_size = i["Size"] 177 | if file_size > 1048576: 178 | download_size_print = str(round(file_size / 1048576, 2)) + "M" 179 | else: 180 | download_size_print = str(round(file_size / 1024, 2)) + "K" 181 | 182 | if i["Type"] == 0: 183 | print( 184 | "\033[33m" + "编号:", 185 | self.list.index(i) + 1, 186 | "\033[0m \t\t" + download_size_print + "\t\t\033[36m", 187 | i["FileName"], 188 | "\033[0m", 189 | ) 190 | elif i["Type"] == 1: 191 | print( 192 | "\033[35m" + "编号:", 193 | self.list.index(i) + 1, 194 | " \t\t\033[36m", 195 | i["FileName"], 196 | "\033[0m", 197 | ) 198 | 199 | print("--------------------") 200 | 201 | # fileNumber 从0开始,0为第一个文件,传入时需要减一 !!! 202 | def link(self, file_number, showlink=True): 203 | file_detail = self.list[file_number] 204 | type_detail = file_detail["Type"] 205 | if type_detail == 1: 206 | down_request_url = "https://www.123pan.com/a/api/file/batch_download_info" 207 | down_request_data = {"fileIdList": [{"fileId": int(file_detail["FileId"])}]} 208 | 209 | else: 210 | down_request_url = "https://www.123pan.com/a/api/file/download_info" 211 | down_request_data = { 212 | "driveId": 0, 213 | "etag": file_detail["Etag"], 214 | "fileId": file_detail["FileId"], 215 | "s3keyFlag": file_detail["S3KeyFlag"], 216 | "type": file_detail["Type"], 217 | "fileName": file_detail["FileName"], 218 | "size": file_detail["Size"], 219 | } 220 | # print(down_request_data) 221 | 222 | sign = getSign("/a/api/file/download_info") 223 | 224 | link_res = requests.post( 225 | down_request_url, 226 | headers=self.header_logined, 227 | params={sign[0]: sign[1]}, 228 | data=down_request_data, 229 | timeout=10 230 | ) 231 | # print(linkRes.text) 232 | res_code_download = link_res.json()["code"] 233 | if res_code_download != 0: 234 | print("code = 3 Error:" + str(res_code_download)) 235 | # print(linkRes.json()) 236 | return res_code_download 237 | download_link_base64 = link_res.json()["data"]["DownloadUrl"] 238 | base64_url = re.findall("params=(.*)&", download_link_base64)[0] 239 | # print(Base64Url) 240 | down_load_url = base64.b64decode(base64_url) 241 | down_load_url = down_load_url.decode("utf-8") 242 | 243 | next_to_get = requests.get(down_load_url,timeout=10).json() 244 | redirect_url = next_to_get["data"]["redirect_url"] 245 | if showlink: 246 | print(redirect_url) 247 | 248 | return redirect_url 249 | 250 | def download(self, file_number): 251 | file_detail = self.list[file_number] 252 | down_load_url = self.link(file_number, showlink=False) 253 | file_name = file_detail["FileName"] # 文件名 254 | if os.path.exists(file_name): 255 | print("文件 " + file_name + " 已存在,是否要覆盖?") 256 | sure_download = input("输入1覆盖,2取消:") 257 | if sure_download != "1": 258 | return 259 | down = requests.get(down_load_url, stream=True, timeout=10) 260 | 261 | file_size = int(down.headers["Content-Length"]) # 文件大小 262 | content_size = int(file_size) # 文件总大小 263 | data_count = 0 # 当前已传输的大小 264 | if file_size > 1048576: 265 | size_print_download = str(round(file_size / 1048576, 2)) + "M" 266 | else: 267 | size_print_download = str(round(file_size / 1024, 2)) + "K" 268 | print(file_name + " " + size_print_download) 269 | time1 = time.time() 270 | time_temp = time1 271 | data_count_temp = 0 272 | with open(file_name, "wb") as f: 273 | for i in down.iter_content(1024): 274 | f.write(i) 275 | done_block = int((data_count / content_size) * 50) 276 | data_count = data_count + len(i) 277 | # 实时进度条进度 278 | now_jd = (data_count / content_size) * 100 279 | # %% 表示% 280 | # 测速 281 | time1 = time.time() 282 | pass_time = time1 - time_temp 283 | if pass_time > 1: 284 | time_temp = time1 285 | pass_data = int(data_count) - int(data_count_temp) 286 | data_count_temp = data_count 287 | speed = pass_data / int(pass_time) 288 | speed_m = speed / 1048576 289 | if speed_m > 1: 290 | speed_print = str(round(speed_m, 2)) + "M/S" 291 | else: 292 | speed_print = str(round(speed_m * 1024, 2)) + "K/S" 293 | print( 294 | "\r [%s%s] %d%% %s" 295 | % ( 296 | done_block * "█", 297 | " " * (50 - 1 - done_block), 298 | now_jd, 299 | speed_print, 300 | ), 301 | end="", 302 | ) 303 | elif data_count == content_size: 304 | print("\r [%s%s] %d%% %s" % (50 * "█", "", 100, ""), end="") 305 | print("\nok") 306 | 307 | def recycle(self): 308 | recycle_id = 0 309 | url = ( 310 | "https://www.123pan.com/a/api/file/list/new?driveId=0&limit=100&next=0" 311 | "&orderBy=fileId&orderDirection=desc&parentFileId=" 312 | + str(recycle_id) 313 | + "&trashed=true&&Page=1" 314 | ) 315 | recycle_res = requests.get(url, headers=self.header_logined, timeout=10) 316 | json_recycle = recycle_res.json() 317 | recycle_list = json_recycle["data"]["InfoList"] 318 | self.recycle_list = recycle_list 319 | 320 | # fileNumber 从0开始,0为第一个文件,传入时需要减一 !!! 321 | def delete_file(self, file, by_num=True, operation=True): 322 | # operation = 'true' 删除 , operation = 'false' 恢复 323 | if by_num: 324 | print(file) 325 | if not str(file).isdigit(): 326 | print("请输入数字") 327 | return -1 328 | if 0 <= file < len(self.list): 329 | file_detail = self.list[file] 330 | else: 331 | print("不在合理范围内") 332 | return 333 | else: 334 | if file in self.list: 335 | file_detail = file 336 | else: 337 | print("文件不存在") 338 | return 339 | data_delete = { 340 | "driveId": 0, 341 | "fileTrashInfoList": file_detail, 342 | "operation": operation, 343 | } 344 | delete_res = requests.post( 345 | "https://www.123pan.com/a/api/file/trash", 346 | data=json.dumps(data_delete), 347 | headers=self.header_logined, 348 | timeout=10 349 | ) 350 | dele_json = delete_res.json() 351 | print(dele_json) 352 | message = dele_json["message"] 353 | print(message) 354 | 355 | def share(self): 356 | file_id_list = "" 357 | share_name_list = [] 358 | add = "1" 359 | while str(add) == "1": 360 | share_num = input("分享文件的编号:") 361 | num_test2 = share_num.isdigit() 362 | if num_test2: 363 | share_num = int(share_num) 364 | if 0 < share_num < len(self.list) + 1: 365 | share_id = self.list[int(share_num) - 1]["FileId"] 366 | share_name = self.list[int(share_num) - 1]["FileName"] 367 | share_name_list.append(share_name) 368 | print(share_name_list) 369 | file_id_list = file_id_list + str(share_id) + "," 370 | add = input("输入1添加文件,0发起分享,其他取消") 371 | else: 372 | print("请输入数字,,") 373 | add = "1" 374 | if str(add) == "0": 375 | share_pwd = input("提取码,不设留空:") 376 | file_id_list = file_id_list.strip(",") 377 | data = { 378 | "driveId": 0, 379 | "expiration": "2024-02-09T11:42:45+08:00", 380 | "fileIdList": file_id_list, 381 | "shareName": "我的分享", 382 | "sharePwd": share_pwd, 383 | } 384 | share_res = requests.post( 385 | "https://www.123pan.com/a/api/share/create", 386 | headers=self.header_logined, 387 | data=json.dumps(data), 388 | timeout=10 389 | ) 390 | share_res_json = share_res.json() 391 | message = share_res_json["message"] 392 | print(message) 393 | share_key = share_res_json["data"]["ShareKey"] 394 | share_url = "https://www.123pan.com/s/" + share_key 395 | print("分享链接:\n" + share_url + "提取码:" + share_pwd) 396 | else: 397 | print("退出分享") 398 | 399 | def up_load(self, file_path): 400 | file_path = file_path.replace('"', "") 401 | file_path = file_path.replace("\\", "/") 402 | file_name = file_path.split("/")[-1] 403 | print("文件名:", file_name) 404 | if not os.path.exists(file_path): 405 | print("文件不存在,请检查路径是否正确") 406 | return 407 | if os.path.isdir(file_path): 408 | print("暂不支持文件夹上传") 409 | return 410 | fsize = os.path.getsize(file_path) 411 | with open(file_path, "rb") as f: 412 | md5 = hashlib.md5() 413 | while True: 414 | data = f.read(64 * 1024) 415 | if not data: 416 | break 417 | md5.update(data) 418 | readable_hash = md5.hexdigest() 419 | 420 | list_up_request = { 421 | "driveId": 0, 422 | "etag": readable_hash, 423 | "fileName": file_name, 424 | "parentFileId": self.parent_file_id, 425 | "size": fsize, 426 | "type": 0, 427 | "duplicate": 0, 428 | } 429 | 430 | sign = getSign("/b/api/file/upload_request") 431 | up_res = requests.post( 432 | "https://www.123pan.com/b/api/file/upload_request", 433 | headers=self.header_logined, 434 | params={sign[0]: sign[1]}, 435 | data=list_up_request, 436 | timeout=10 437 | ) 438 | up_res_json = up_res.json() 439 | res_code_up = up_res_json["code"] 440 | if res_code_up == 5060: 441 | sure_upload = input("检测到1个同名文件,输入1覆盖,2保留两者,0取消:") 442 | if sure_upload == "1": 443 | list_up_request["duplicate"] = 1 444 | 445 | elif sure_upload == "2": 446 | list_up_request["duplicate"] = 2 447 | else: 448 | print("取消上传") 449 | return 450 | sign = getSign("/b/api/file/upload_request") 451 | up_res = requests.post( 452 | "https://www.123pan.com/b/api/file/upload_request", 453 | headers=self.header_logined, 454 | params={sign[0]: sign[1]}, 455 | data=json.dumps(list_up_request), 456 | timeout=10 457 | ) 458 | up_res_json = up_res.json() 459 | res_code_up = up_res_json["code"] 460 | if res_code_up == 0: 461 | # print(upResJson) 462 | # print("上传请求成功") 463 | reuse = up_res_json["data"]["Reuse"] 464 | if reuse: 465 | print("上传成功,文件已MD5复用") 466 | return 467 | else: 468 | print(up_res_json) 469 | print("上传请求失败") 470 | return 471 | 472 | bucket = up_res_json["data"]["Bucket"] 473 | storage_node = up_res_json["data"]["StorageNode"] 474 | upload_key = up_res_json["data"]["Key"] 475 | upload_id = up_res_json["data"]["UploadId"] 476 | up_file_id = up_res_json["data"]["FileId"] # 上传文件的fileId,完成上传后需要用到 477 | print("上传文件的fileId:", up_file_id) 478 | 479 | # 获取已将上传的分块 480 | start_data = { 481 | "bucket": bucket, 482 | "key": upload_key, 483 | "uploadId": upload_id, 484 | "storageNode": storage_node, 485 | } 486 | start_res = requests.post( 487 | "https://www.123pan.com/b/api/file/s3_list_upload_parts", 488 | headers=self.header_logined, 489 | data=json.dumps(start_data), 490 | timeout=10 491 | ) 492 | start_res_json = start_res.json() 493 | res_code_up = start_res_json["code"] 494 | if res_code_up == 0: 495 | # print(startResJson) 496 | pass 497 | else: 498 | print(start_data) 499 | print(start_res_json) 500 | 501 | print("获取传输列表失败") 502 | return 503 | 504 | # 分块,每一块取一次链接,依次上传 505 | block_size = 5242880 506 | with open(file_path, "rb") as f: 507 | part_number_start = 1 508 | put_size = 0 509 | while True: 510 | data = f.read(block_size) 511 | 512 | precent = round(put_size / fsize, 2) 513 | print("\r已上传:" + str(precent * 100) + "%", end="") 514 | put_size = put_size + len(data) 515 | 516 | if not data: 517 | break 518 | get_link_data = { 519 | "bucket": bucket, 520 | "key": upload_key, 521 | "partNumberEnd": part_number_start + 1, 522 | "partNumberStart": part_number_start, 523 | "uploadId": upload_id, 524 | "StorageNode": storage_node, 525 | } 526 | 527 | get_link_url = ( 528 | "https://www.123pan.com/b/api/file/s3_repare_upload_parts_batch" 529 | ) 530 | get_link_res = requests.post( 531 | get_link_url, 532 | headers=self.header_logined, 533 | data=json.dumps(get_link_data), 534 | timeout=10 535 | ) 536 | get_link_res_json = get_link_res.json() 537 | res_code_up = get_link_res_json["code"] 538 | if res_code_up == 0: 539 | # print("获取链接成功") 540 | pass 541 | else: 542 | print("获取链接失败") 543 | # print(getLinkResJson) 544 | return 545 | # print(getLinkResJson) 546 | upload_url = get_link_res_json["data"]["presignedUrls"][ 547 | str(part_number_start) 548 | ] 549 | # print("上传链接",uploadUrl) 550 | requests.put(upload_url, data=data, timeout=10) 551 | # print("put") 552 | 553 | part_number_start = part_number_start + 1 554 | 555 | print("\n处理中") 556 | # 完成标志 557 | # 1.获取已上传的块 558 | uploaded_list_url = "https://www.123pan.com/b/api/file/s3_list_upload_parts" 559 | uploaded_comp_data = { 560 | "bucket": bucket, 561 | "key": upload_key, 562 | "uploadId": upload_id, 563 | "storageNode": storage_node, 564 | } 565 | # print(uploadedCompData) 566 | requests.post( 567 | uploaded_list_url, 568 | headers=self.header_logined, 569 | data=json.dumps(uploaded_comp_data), 570 | timeout=10 571 | ) 572 | compmultipart_up_url = ( 573 | "https://www.123pan.com/b/api/file/s3_complete_multipart_upload" 574 | ) 575 | requests.post( 576 | compmultipart_up_url, 577 | headers=self.header_logined, 578 | data=json.dumps(uploaded_comp_data), 579 | timeout=10 580 | ) 581 | 582 | # 3.报告完成上传,关闭upload session 583 | if fsize > 64 * 1024 * 1024: 584 | time.sleep(3) 585 | close_up_session_url = "https://www.123pan.com/b/api/file/upload_complete" 586 | close_up_session_data = {"fileId": up_file_id} 587 | # print(closeUpSessionData) 588 | close_up_session_res = requests.post( 589 | close_up_session_url, 590 | headers=self.header_logined, 591 | data=json.dumps(close_up_session_data), 592 | timeout=10 593 | ) 594 | close_res_json = close_up_session_res.json() 595 | # print(closeResJson) 596 | res_code_up = close_res_json["code"] 597 | if res_code_up == 0: 598 | print("上传成功") 599 | else: 600 | print("上传失败") 601 | print(close_res_json) 602 | return 603 | 604 | # dirId 就是 fileNumber,从0开始,0为第一个文件,传入时需要减一 !!!(好像文件夹都排在前面) 605 | def cd(self, dir_num): 606 | if not dir_num.isdigit(): 607 | if dir_num == "..": 608 | if len(self.parent_file_list) > 1: 609 | self.parent_file_list.pop() 610 | self.parent_file_id = self.parent_file_list[-1] 611 | self.get_dir() 612 | self.show() 613 | else: 614 | print("已经是根目录") 615 | return 616 | if dir_num == "/": 617 | self.parent_file_id = 0 618 | self.parent_file_list = [0] 619 | self.get_dir() 620 | self.show() 621 | return 622 | print("输入错误") 623 | return 624 | dir_num = int(dir_num) - 1 625 | if dir_num >= (len(self.list) - 1) or dir_num < 0: 626 | print("输入错误") 627 | return 628 | if self.list[dir_num]["Type"] != 1: 629 | print("不是文件夹") 630 | return 631 | self.parent_file_id = self.list[dir_num]["FileId"] 632 | self.parent_file_list.append(self.parent_file_id) 633 | self.get_dir() 634 | self.show() 635 | 636 | def cdById(self, file_id): 637 | self.parent_file_id = file_id 638 | self.parent_file_list.append(self.parent_file_id) 639 | self.get_dir() 640 | self.get_dir() 641 | self.show() 642 | 643 | def read_ini( 644 | self, 645 | user_name, 646 | pass_word, 647 | input_pwd, 648 | authorization="", 649 | ): 650 | try: 651 | with open("123pan.txt", "r",encoding="utf-8") as f: 652 | text = f.read() 653 | text = json.loads(text) 654 | user_name = text["userName"] 655 | pass_word = text["passWord"] 656 | authorization = text["authorization"] 657 | 658 | except: 659 | print("获取配置失败,重新登录") 660 | 661 | if user_name == "" or pass_word == "": 662 | if input_pwd: 663 | user_name = input("userName:") 664 | pass_word = input("passWord:") 665 | authorization = "" 666 | else: 667 | raise Exception("禁止输入模式下,没有账号或密码") 668 | 669 | self.user_name = user_name 670 | self.password = pass_word 671 | self.authorization = authorization 672 | 673 | def mkdir(self, dirname, remakedir=False): 674 | if not remakedir: 675 | for i in self.list: 676 | if i["FileName"] == dirname: 677 | print("文件夹已存在") 678 | return i["FileId"] 679 | 680 | url = "https://www.123pan.com/a/api/file/upload_request" 681 | data_mk = { 682 | "driveId": 0, 683 | "etag": "", 684 | "fileName": dirname, 685 | "parentFileId": self.parent_file_id, 686 | "size": 0, 687 | "type": 1, 688 | "duplicate": 1, 689 | "NotReuse": True, 690 | "event": "newCreateFolder", 691 | "operateType": 1, 692 | } 693 | sign = getSign("/a/api/file/upload_request") 694 | res_mk = requests.post( 695 | url, 696 | headers=self.header_logined, 697 | data=json.dumps(data_mk), 698 | params={sign[0]: sign[1]}, 699 | timeout=10 700 | ) 701 | try: 702 | res_json = res_mk.json() 703 | print(res_json) 704 | except json.decoder.JSONDecodeError: 705 | print("创建失败") 706 | print(res_mk.text) 707 | return 708 | code_mkdir = res_json["code"] 709 | 710 | if code_mkdir == 0: 711 | print("创建成功: ", res_json["data"]["FileId"]) 712 | self.get_dir() 713 | return res_json["data"]["Info"]["FileId"] 714 | print("创建失败") 715 | print(res_json) 716 | return 717 | 718 | 719 | if __name__ == "__main__": 720 | pan = Pan123(readfile=True, input_pwd=True) 721 | pan.show() 722 | while True: 723 | command = input("\033[91m >\033[0m") 724 | if command == "ls": 725 | pan.show() 726 | if command == "re": 727 | code = pan.get_dir() 728 | if code == 0: 729 | print("刷新目录成功") 730 | pan.show() 731 | if command.isdigit(): 732 | if int(command) > len(pan.list) or int(command) < 1: 733 | print("输入错误") 734 | continue 735 | if pan.list[int(command) - 1]["Type"] == 1: 736 | pan.cdById(pan.list[int(command) - 1]["FileId"]) 737 | else: 738 | size = pan.list[int(command) - 1]["Size"] 739 | if size > 1048576: 740 | size_print_show = str(round(size / 1048576, 2)) + "M" 741 | else: 742 | size_print_show = str(round(size / 1024, 2)) + "K" 743 | # print(pan.list[int(command) - 1]) 744 | name = pan.list[int(command) - 1]["FileName"] 745 | print(name + " " + size_print_show) 746 | print("press 1 to download now: ", end="") 747 | sure = input() 748 | if sure == "1": 749 | pan.download(int(command) - 1) 750 | elif command[0:9] == "download ": 751 | if command[9:].isdigit(): 752 | if int(command[9:]) > len(pan.list) or int(command[9:]) < 1: 753 | print("输入错误") 754 | continue 755 | pan.download(int(command[9:]) - 1) 756 | else: 757 | print("输入错误") 758 | elif command == "exit": 759 | break 760 | elif command == "log": 761 | pan.login() 762 | pan.get_dir() 763 | pan.show() 764 | 765 | elif command[0:5] == "link ": 766 | if command[5:].isdigit(): 767 | if int(command[5:]) > len(pan.list) or int(command[5:]) < 1: 768 | print("输入错误") 769 | continue 770 | pan.link(int(command[5:]) - 1) 771 | else: 772 | print("输入错误") 773 | elif command == "upload": 774 | filepath = input("请输入文件路径:") 775 | pan.up_load(filepath) 776 | pan.get_dir() 777 | pan.show() 778 | elif command == "share": 779 | pan.share() 780 | elif command[0:6] == "delete": 781 | if command == "delete": 782 | print("请输入要删除的文件编号:", end="") 783 | fileNumber = input() 784 | else: 785 | if command[6] == " ": 786 | fileNumber = command[7:] 787 | else: 788 | print("输入错误") 789 | continue 790 | if fileNumber == "": 791 | print("请输入要删除的文件编号:", end="") 792 | fileNumber = input() 793 | else: 794 | fileNumber = fileNumber[0:] 795 | if fileNumber.isdigit(): 796 | if int(fileNumber) > len(pan.list) or int(fileNumber) < 1: 797 | print("输入错误") 798 | continue 799 | pan.delete_file(int(fileNumber) - 1) 800 | pan.get_dir() 801 | pan.show() 802 | else: 803 | print("输入错误") 804 | 805 | elif command[:3] == "cd ": 806 | path = command[3:] 807 | pan.cd(path) 808 | elif command[0:5] == "mkdir": 809 | if command == "mkdir": 810 | newPath = input("请输入目录名:") 811 | else: 812 | newPath = command[6:] 813 | if newPath == "": 814 | newPath = input("请输入目录名:") 815 | else: 816 | newPath = newPath[0:] 817 | print(pan.mkdir(newPath)) 818 | 819 | elif command == "reload": 820 | pan.read_ini("", "", True) 821 | print("读取成功") 822 | pan.get_dir() 823 | pan.show() 824 | --------------------------------------------------------------------------------