fundrive-quark 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fundrive-quark might be problematic. Click here for more details.

@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: fundrive-quark
3
+ Version: 1.0.1
4
+ Summary: fundrive-quark
5
+ Author-email: niuliangtao <farfarfun@qq.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: funutil>=1.0.46
9
+
10
+ # fardrive-quark
@@ -0,0 +1 @@
1
+ # fardrive-quark
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "fundrive-quark"
3
+ version = "1.0.1"
4
+ description = "fundrive-quark"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ dependencies = [ "funutil>=1.0.46",]
8
+ [[project.authors]]
9
+ name = "niuliangtao"
10
+ email = "farfarfun@qq.com"
11
+
12
+ [tool.setuptools]
13
+ license-files = []
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: fundrive-quark
3
+ Version: 1.0.1
4
+ Summary: fundrive-quark
5
+ Author-email: niuliangtao <farfarfun@qq.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: funutil>=1.0.46
9
+
10
+ # fardrive-quark
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/fundrive_quark.egg-info/PKG-INFO
4
+ src/fundrive_quark.egg-info/SOURCES.txt
5
+ src/fundrive_quark.egg-info/dependency_links.txt
6
+ src/fundrive_quark.egg-info/requires.txt
7
+ src/fundrive_quark.egg-info/top_level.txt
8
+ src/fundrives/__init__.py
9
+ src/fundrives/quark/__init__.py
10
+ src/fundrives/quark/manage.py
@@ -0,0 +1 @@
1
+ funutil>=1.0.46
File without changes
File without changes
@@ -0,0 +1,619 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import os
4
+ import random
5
+ import re
6
+ import shutil
7
+ import string
8
+ import time
9
+ from datetime import datetime
10
+ from typing import List, Dict, Tuple, Any, Union
11
+
12
+ import requests
13
+ from funutil import getLogger, deep_get
14
+
15
+ logger = getLogger("fundrive")
16
+
17
+
18
+ def get_id_from_url(url) -> str:
19
+ """pwd_id"""
20
+ pattern = r"/s/(\w+)"
21
+ match = re.search(pattern, url)
22
+ if match:
23
+ return match.group(1)
24
+ return ""
25
+
26
+
27
+ def safe_copy(src, dst):
28
+ if not os.path.exists(src):
29
+ print(f"源文件不存在,跳过复制:{src}")
30
+ return
31
+
32
+ if os.path.exists(dst):
33
+ os.remove(dst)
34
+ print(f"目标文件已存在,已删除:{dst}")
35
+
36
+ try:
37
+ shutil.copy(src, dst)
38
+ print(f"文件已复制到:{dst}")
39
+ except Exception as e:
40
+ print("备份share_url.txt文件错误,", e)
41
+
42
+
43
+ def generate_random_code(length=4):
44
+ characters = string.ascii_letters + string.digits
45
+ random_code = "".join(random.choice(characters) for _ in range(length))
46
+ return random_code
47
+
48
+
49
+ def get_datetime(
50
+ timestamp: Union[int, float, None] = None, fmt: str = "%Y-%m-%d %H:%M:%S"
51
+ ) -> str:
52
+ if timestamp is None or not isinstance(timestamp, (int, float)):
53
+ return datetime.today().strftime(fmt)
54
+ else:
55
+ dt = datetime.fromtimestamp(timestamp)
56
+ formatted_time = dt.strftime(fmt)
57
+ return formatted_time
58
+
59
+
60
+ class QuarkPanManage:
61
+ def __init__(self, cookies, *args, **kwargs) -> None:
62
+ # self.base_url = 'https://drive.quark.cn/1/clouddrive'
63
+ self.base_url = "https://drive-pc.quark.cn/1/clouddrive"
64
+
65
+ self.headers: Dict[str, str] = {
66
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)"
67
+ " Chrome/94.0.4606.71 Safari/537.36 Core/1.94.225.400 QQBrowser/12.2.5544.400",
68
+ "origin": "https://pan.quark.cn",
69
+ "referer": "https://pan.quark.cn/",
70
+ "accept-language": "zh-CN,zh;q=0.9",
71
+ "cookie": cookies,
72
+ }
73
+
74
+ @staticmethod
75
+ def get_pwd_id(share_url: str) -> str:
76
+ return share_url.split("?")[0].split("/s/")[1]
77
+
78
+ @staticmethod
79
+ def extract_urls(text: str) -> list:
80
+ url_pattern = r'https?://[^\s<>"]+|www\.[^\s<>"]+'
81
+ return re.findall(url_pattern, text)[0]
82
+
83
+ def request(
84
+ self,
85
+ uri,
86
+ method="GET",
87
+ params=None,
88
+ headers=None,
89
+ data=None,
90
+ timeout=10,
91
+ *args,
92
+ **kwargs,
93
+ ):
94
+ url = f"{self.base_url}/{uri}"
95
+ params = params or {}
96
+ params.update(
97
+ {
98
+ "pr": "ucpro",
99
+ "fr": "pc",
100
+ "uc_param_str": "",
101
+ "__dt": random.randint(100, 9999),
102
+ "__t": int(time.time()) * 1000,
103
+ }
104
+ )
105
+ return requests.request(
106
+ method,
107
+ url,
108
+ params=params,
109
+ headers=headers or self.headers,
110
+ json=data,
111
+ timeout=timeout,
112
+ *args,
113
+ **kwargs,
114
+ ).json()
115
+
116
+ def get_stoken(self, pwd_id: str) -> str:
117
+ data = {"pwd_id": pwd_id, "passcode": ""}
118
+ json_data = self.request(
119
+ "share/sharepage/token",
120
+ "post",
121
+ data=data,
122
+ )
123
+ if json_data["status"] == 200 and json_data["data"]:
124
+ stoken = json_data["data"]["stoken"]
125
+ else:
126
+ stoken = ""
127
+ logger.info(f"文件转存失败,{json_data['message']}")
128
+ return stoken
129
+
130
+ def get_detail(
131
+ self,
132
+ pwd_id: str,
133
+ stoken: str,
134
+ pdir_fid: str = "0",
135
+ size=50,
136
+ sort="file_type:asc,updated_at:desc",
137
+ ) -> Tuple[str, List[Dict[str, Union[int, str]]]]:
138
+ file_list: List[Dict[str, Union[int, str]]] = []
139
+ for page in range(1, 100):
140
+ params = {
141
+ "pwd_id": pwd_id,
142
+ "stoken": stoken,
143
+ "pdir_fid": pdir_fid,
144
+ "force": "0",
145
+ "_page": page,
146
+ "_size": size,
147
+ "_sort": sort,
148
+ }
149
+
150
+ json_data = self.request("share/sharepage/detail", "get", params=params)
151
+ is_owner = json_data["data"]["is_owner"]
152
+ _total = json_data["metadata"]["_total"]
153
+ if _total < 1:
154
+ return is_owner, file_list
155
+
156
+ _size = json_data["metadata"]["_size"] # 每页限制数量
157
+ _count = json_data["metadata"]["_count"] # 当前页数量
158
+
159
+ _list = json_data["data"]["list"]
160
+
161
+ for file in _list:
162
+ file_list.append(
163
+ {
164
+ "fid": file["fid"],
165
+ "file_name": file["file_name"],
166
+ "file_type": file["file_type"],
167
+ "dir": file["dir"],
168
+ "pdir_fid": file["pdir_fid"],
169
+ "include_items": file["include_items"]
170
+ if "include_items" in file
171
+ else "",
172
+ "share_fid_token": file["share_fid_token"],
173
+ "status": file["status"],
174
+ }
175
+ )
176
+ if _total <= _size or _count < _size:
177
+ return is_owner, file_list
178
+
179
+ def get_user_info(self) -> str:
180
+ params = {
181
+ "fr": "pc",
182
+ "platform": "pc",
183
+ }
184
+ return requests.get(
185
+ "https://pan.quark.cn/account/info", params=params, headers=self.headers
186
+ ).json()
187
+
188
+ def create_dir(self, pdir_name="新建文件夹", pdir_fid="") -> None:
189
+ json_data = {
190
+ "pdir_fid": pdir_fid,
191
+ "file_name": pdir_name,
192
+ "dir_path": "",
193
+ "dir_init_lock": False,
194
+ }
195
+ return self.request("file", "post", data=json_data)
196
+
197
+ def save_shared(
198
+ self,
199
+ share_url: str,
200
+ folder_id: Union[str, None] = None,
201
+ ) -> None:
202
+ logger.info(f"文件分享链接:{share_url}")
203
+ pwd_id = self.get_pwd_id(share_url)
204
+ stoken = self.get_stoken(pwd_id)
205
+ if not stoken:
206
+ return
207
+ is_owner, data_list = self.get_detail(pwd_id, stoken)
208
+ files_count = 0
209
+ folders_count = 0
210
+ files_list: List[str] = []
211
+ folders_list: List[str] = []
212
+ files_id_list = []
213
+
214
+ if data_list:
215
+ total_files_count = len(data_list)
216
+ for data in data_list:
217
+ if data["dir"]:
218
+ folders_count += 1
219
+ folders_list.append(data["file_name"])
220
+ else:
221
+ files_count += 1
222
+ files_list.append(data["file_name"])
223
+ files_id_list.append((data["fid"], data["file_name"]))
224
+
225
+ logger.info(
226
+ f"转存总数:{total_files_count},文件数:{files_count},文件夹数:{folders_count} | 支持嵌套"
227
+ )
228
+ logger.info(f"文件转存列表:{files_list}")
229
+ logger.info(f"文件夹转存列表:{folders_list}")
230
+
231
+ fid_list = [i["fid"] for i in data_list]
232
+ share_fid_token_list = [i["share_fid_token"] for i in data_list]
233
+
234
+ if not folder_id:
235
+ logger.info(
236
+ "保存目录ID不合法,请重新获取,如果无法获取,请输入0作为文件夹ID"
237
+ )
238
+ return
239
+
240
+ if is_owner == 1:
241
+ logger.info("网盘中已经存在该文件,无需再次转存")
242
+ return
243
+ task_id = self.get_share_save_task_id(
244
+ pwd_id, stoken, fid_list, share_fid_token_list, to_pdir_fid=folder_id
245
+ )
246
+ self.submit_task(task_id)
247
+
248
+ def get_file_download_url(self, fid: str) -> None:
249
+ data = {"fids": [fid]}
250
+ json_data = self.request("file/download", "post", data=data)
251
+ data_list = json_data.get("data", None)
252
+
253
+ if json_data["status"] != 200:
254
+ logger.info(
255
+ f"文件下载地址列表获取失败, {json_data['message']}", error_msg=True
256
+ )
257
+ return
258
+ elif data_list:
259
+ logger.info("文件下载地址列表获取成功")
260
+ return data_list[0]["download_url"]
261
+
262
+ def get_share_save_task_id(
263
+ self,
264
+ pwd_id: str,
265
+ stoken: str,
266
+ first_ids: List[str],
267
+ share_fid_tokens: List[str],
268
+ to_pdir_fid: str = "0",
269
+ ) -> str:
270
+ data = {
271
+ "fid_list": first_ids,
272
+ "fid_token_list": share_fid_tokens,
273
+ "to_pdir_fid": to_pdir_fid,
274
+ "pwd_id": pwd_id,
275
+ "stoken": stoken,
276
+ "pdir_fid": "0",
277
+ "scene": "link",
278
+ }
279
+
280
+ response = self.request("share/sharepage/save", "post", data=data)
281
+ json_data = response.json()
282
+ task_id = json_data["data"]["task_id"]
283
+ logger.info(f"获取任务ID:{task_id}")
284
+ return task_id
285
+
286
+ def submit_task(
287
+ self, task_id: str, retry: int = 50
288
+ ) -> Union[bool, Dict[str, Union[str, Dict[str, Union[int, str]]]]]:
289
+ for i in range(retry):
290
+ # 随机暂停100-50毫秒
291
+ time.sleep(random.randint(500, 1000) / 1000)
292
+ logger.info(f"第{i + 1}次提交任务")
293
+ params = {"task_id": task_id, "retry_index": i}
294
+ json_data = self.request("task", "get", headers=self.headers, params=params)
295
+
296
+ if json_data["message"] != "ok":
297
+ if (
298
+ json_data["code"] == 32003
299
+ and "capacity limit" in json_data["message"]
300
+ ):
301
+ logger.info(
302
+ "转存失败,网盘容量不足!请注意当前已成功保存的个数,避免重复保存",
303
+ )
304
+ elif json_data["code"] == 41013:
305
+ logger.info(
306
+ f"网盘文件夹不存在,请重新运行按3切换保存目录后重试!",
307
+ )
308
+ else:
309
+ logger.info(
310
+ f"错误信息:{json_data['message']}",
311
+ )
312
+ continue
313
+
314
+ if json_data["data"]["status"] != 2:
315
+ continue
316
+
317
+ if json_data["data"]["task_title"] == "分享-转存":
318
+ logger.info(f"结束任务ID:{task_id}")
319
+ logger.info(
320
+ f"文件保存位置:{deep_get(json_data, 'data', 'save_as', 'to_pdir_name') or '根目录'} 文件夹"
321
+ )
322
+ return json_data
323
+
324
+ def get_share_task_id(
325
+ self,
326
+ fid: str,
327
+ file_name: str,
328
+ url_type: int = 1,
329
+ expired_type: int = 2,
330
+ password: str = "",
331
+ ) -> str:
332
+ json_data = {
333
+ "fid_list": [fid],
334
+ "title": file_name,
335
+ "url_type": url_type,
336
+ "expired_type": expired_type,
337
+ }
338
+ if url_type == 2:
339
+ json_data["passcode"] = password or generate_random_code()
340
+ json_data = self.request("share", "post", json=json_data)
341
+ return json_data["data"]["task_id"]
342
+
343
+ def get_share_id(self, task_id: str) -> str:
344
+ params = {
345
+ "task_id": task_id,
346
+ "retry_index": "0",
347
+ }
348
+ json_data = self.request("task", "get", params=params)
349
+ return json_data["data"]["share_id"]
350
+
351
+ def submit_share(self, share_id: str) -> None:
352
+ json_data = {
353
+ "share_id": share_id,
354
+ }
355
+ json_data = self.request(
356
+ "share/password",
357
+ "post",
358
+ data=json_data,
359
+ )
360
+ share_url = json_data["data"]["share_url"]
361
+ if "passcode" in json_data["data"]:
362
+ share_url = share_url + f"?pwd={json_data['data']['passcode']}"
363
+ return share_url
364
+
365
+ def share(
366
+ self,
367
+ share_url: str,
368
+ folder_id: Union[str, None] = None,
369
+ url_type: int = 1,
370
+ expired_type: int = 2,
371
+ password: str = "",
372
+ ) -> None:
373
+ first_dir = ""
374
+ second_dir = ""
375
+ try:
376
+ logger.info(f"文件夹网页地址:{share_url}")
377
+ pwd_id = share_url.rsplit("/", maxsplit=1)[1].split("-")[0]
378
+
379
+ first_page = 1
380
+ n = 0
381
+ error = 0
382
+ os.makedirs("share", exist_ok=True)
383
+
384
+ while True:
385
+ json_data = self.get_file_list(
386
+ pwd_id, page=first_page, size=50, fetch_total=True
387
+ )
388
+ for i1 in json_data["data"]["list"]:
389
+ if not i1["dir"]:
390
+ continue
391
+
392
+ first_dir = i1["file_name"]
393
+ second_page = 1
394
+ while True:
395
+ logger.info(
396
+ f"正在获取{first_dir}第{first_page}页,二级目录第{second_page}页,目前共分享{n}文件"
397
+ )
398
+ json_data2 = self.get_file_list(
399
+ i1["fid"],
400
+ page=second_page,
401
+ size=50,
402
+ fetch_total=True,
403
+ )
404
+ for i2 in json_data2["data"]["list"]:
405
+ if not i2["dir"]:
406
+ continue
407
+
408
+ n += 1
409
+ share_success = False
410
+
411
+ fid = ""
412
+ for i in range(3):
413
+ try:
414
+ second_dir = i2["file_name"]
415
+ logger.info(
416
+ f"{n}.开始分享 {first_dir}/{second_dir} 文件夹"
417
+ )
418
+ random_time = random.choice([0.5, 1, 1.5, 2])
419
+ time.sleep(random_time)
420
+ # print('获取到文件夹ID:', i2['fid'])
421
+ fid = i2["fid"]
422
+ task_id = self.get_share_task_id(
423
+ fid,
424
+ second_dir,
425
+ url_type=url_type,
426
+ expired_type=expired_type,
427
+ password=password,
428
+ )
429
+ share_id = self.get_share_id(task_id)
430
+ share_url = self.submit_share(share_id)
431
+ logger.info(
432
+ f"{n} | {first_dir} | {second_dir} | {share_url}"
433
+ )
434
+ logger.info(
435
+ f"{n}.分享成功 {first_dir}/{second_dir} 文件夹"
436
+ )
437
+ share_success = True
438
+ break
439
+
440
+ except Exception as e:
441
+ share_error_msg = e
442
+ error += 1
443
+
444
+ if not share_success:
445
+ logger.error("分享失败:", share_error_msg)
446
+ logger.error(
447
+ f"{error}.{first_dir}/{second_dir} 文件夹"
448
+ )
449
+ logger.error(
450
+ f"{n} | {first_dir} | {second_dir} | {fid}"
451
+ )
452
+
453
+ second_total = json_data2["metadata"]["_total"]
454
+ second_size = json_data2["metadata"]["_size"]
455
+ second_page = json_data2["metadata"]["_page"]
456
+ if second_size * second_page >= second_total:
457
+ break
458
+ second_page += 1
459
+
460
+ second_total = json_data["metadata"]["_total"]
461
+ second_size = json_data["metadata"]["_size"]
462
+ second_page = json_data["metadata"]["_page"]
463
+ if second_size * second_page >= second_total:
464
+ break
465
+ first_page += 1
466
+ logger.info(f"总共分享了 {n} 个文件夹")
467
+
468
+ except Exception as e:
469
+ print("分享失败:", e)
470
+ logger.error(f"{first_dir}/{second_dir} 文件夹")
471
+
472
+ def share_retry(
473
+ self,
474
+ retry_url: str,
475
+ url_type: int = 1,
476
+ expired_type: int = 2,
477
+ password: str = "",
478
+ ):
479
+ data_list = retry_url.split("\n")
480
+
481
+ error = 0
482
+ error_data = []
483
+ for n, i1 in enumerate(data_list):
484
+ data = i1.split(" | ")
485
+ if data and len(data) == 4:
486
+ first_dir = data[-3]
487
+ second_dir = data[-2]
488
+ fid = data[-1]
489
+ share_success = False
490
+ for i in range(3):
491
+ try:
492
+ task_id = self.get_share_task_id(
493
+ fid,
494
+ second_dir,
495
+ url_type=url_type,
496
+ expired_type=expired_type,
497
+ password=password,
498
+ )
499
+ logger.debug("获取到任务ID:", task_id)
500
+ share_id = self.get_share_id(task_id)
501
+ logger.debug("获取到分享ID:", share_id)
502
+ share_url = self.submit_share(share_id)
503
+ logger.info(f"{n} | {first_dir} | {second_dir} | {share_url}")
504
+ logger.info(f"{n}.分享成功 {first_dir}/{second_dir} 文件夹")
505
+ share_success = True
506
+ break
507
+ except Exception as e:
508
+ logger.error("分享失败:", e)
509
+ error += 1
510
+
511
+ if not share_success:
512
+ error_data.append(i1)
513
+ error_content = "\n".join(error_data)
514
+ logger.error(error_content)
515
+
516
+ def search_file(
517
+ self, file_name, page=1, size=50, sort="file_type:desc,updated_at:desc"
518
+ ):
519
+ logger.info("正在从网盘搜索文件🔍")
520
+ params = {
521
+ "q": file_name,
522
+ "_page": page,
523
+ "_size": size,
524
+ "_sort": sort,
525
+ "_fetch_total": 1,
526
+ "_is_hl": "1",
527
+ }
528
+ return self.request("file/search", params=params)
529
+
530
+ def get_file_list(
531
+ self,
532
+ pdir_fid="0",
533
+ page=1,
534
+ size=100,
535
+ fetch_total=False,
536
+ sort="file_type:asc,file_name:asc",
537
+ ) -> Dict[str, Any]:
538
+ params = {
539
+ "pdir_fid": pdir_fid,
540
+ "_page": page,
541
+ "_size": size,
542
+ "_fetch_total": fetch_total,
543
+ "_fetch_sub_dirs": "1",
544
+ "_sort": sort,
545
+ }
546
+
547
+ return self.request("file/sort", "get", params=params)
548
+
549
+ def del_file(self, file_id):
550
+ logger.debug("正在删除文件")
551
+ data = {"action_type": 2, "filelist": [file_id], "exclude_fids": []}
552
+ return self.request("file/delete", "post", data=data)
553
+
554
+ def store(self, url: str):
555
+ pwd_id = get_id_from_url(url)
556
+ stoken = self.get_stoken(pwd_id)
557
+ detail = self.get_detail(pwd_id, stoken)[1][0]
558
+ file_name = detail.get("title")
559
+
560
+ first_id, share_fid_token, file_type = (
561
+ detail.get("fid"),
562
+ detail.get("share_fid_token"),
563
+ detail.get("file_type"),
564
+ )
565
+ task = self.save_task_id(pwd_id, stoken, first_id, share_fid_token)
566
+ data = self.task(task)
567
+ file_id = data.get("data").get("save_as").get("save_as_top_fids")[0]
568
+ share_task_id = self.share_task_id(file_id, file_name)
569
+ share_id = self.task(share_task_id).get("data").get("share_id")
570
+ share_link = self.get_share_link(share_id)
571
+ logger.info(file_id, file_name, file_type, share_link)
572
+
573
+ def save_task_id(self, pwd_id, stoken, first_id, share_fid_token, to_pdir_fid=0):
574
+ logger.info("获取保存文件的TASKID")
575
+
576
+ data = {
577
+ "fid_list": [first_id],
578
+ "fid_token_list": [share_fid_token],
579
+ "to_pdir_fid": to_pdir_fid,
580
+ "pwd_id": pwd_id,
581
+ "stoken": stoken,
582
+ "pdir_fid": "0",
583
+ "scene": "link",
584
+ }
585
+ response = self.request("share/sharepage/save", "POST", data=data)
586
+ logger.info(response.json())
587
+ task_id = response.json().get("data").get("task_id")
588
+ return task_id
589
+
590
+ def task(self, task_id, trice=10):
591
+ """根据task_id进行任务"""
592
+ logger.info("根据TASKID执行任务")
593
+ trys = 0
594
+ for i in range(trice):
595
+ url = f"task"
596
+ trys += 1
597
+ data = {"task_id": task_id, "retry_index": "range"}
598
+ response = self.request(url, "get", headers=self.headers, data=data)
599
+ logger.info(response)
600
+ if response.get("data").get("status"):
601
+ return response
602
+ return False
603
+
604
+ def share_task_id(self, file_id, file_name):
605
+ """创建分享任务ID"""
606
+ data = {
607
+ "fid_list": [file_id],
608
+ "title": file_name,
609
+ "url_type": 1,
610
+ "expired_type": 1,
611
+ }
612
+ response = self.request("share", "POST", data=data)
613
+ return response.get("data").get("task_id")
614
+
615
+ def get_share_link(self, share_id):
616
+ url = "https://drive-pc.quark.cn/1/clouddrive/share/password?pr=ucpro&fr=pc&uc_param_str="
617
+ data = {"share_id": share_id}
618
+ response = requests.post(url=url, json=data, headers=self.headers)
619
+ return response.json().get("data").get("share_url")