Thaimozhi commited on
Commit
61ced71
·
1 Parent(s): 22d6ca1

feat: support Google Drive folder leeching by running gdown with --folder and matching files recursively

Browse files
Files changed (1) hide show
  1. plugins/file_rename.py +77 -47
plugins/file_rename.py CHANGED
@@ -577,19 +577,22 @@ async def _download_url_with_progress(url: str, output_path: str, task: dict):
577
 
578
  # ── Google Drive Intercept ──
579
  if "drive.google.com" in url:
 
580
  for attempt in range(1, DOWNLOAD_MAX_RETRIES + 1):
581
  try:
582
  task["retry_info"] = f"Initializing Google Drive (Attempt {attempt})" if attempt > 1 else None
583
- await _update_leech_status(task, "Downloading (Google Drive)", force=True)
 
584
 
585
  # Use gdown in a temporary directory to auto-extract the real filename
586
  tmp_gdrive_dir = output_path + "_gdrive_tmp"
587
  os.makedirs(tmp_gdrive_dir, exist_ok=True)
588
 
589
- gdown_cmd = [
590
- "gdown",
591
- url
592
- ]
 
593
  proc = await asyncio.create_subprocess_exec(
594
  *gdown_cmd, cwd=tmp_gdrive_dir, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
595
  )
@@ -600,17 +603,36 @@ async def _download_url_with_progress(url: str, output_path: str, task: dict):
600
  raise asyncio.CancelledError()
601
 
602
  if proc.returncode == 0:
603
- downloaded_files = os.listdir(tmp_gdrive_dir)
604
- if downloaded_files:
605
- true_name = downloaded_files[0]
606
- # Overwrite the placeholder "view" name with the real Google Drive filename
607
- task["display_name"] = true_name
608
- shutil.move(os.path.join(tmp_gdrive_dir, true_name), output_path)
 
 
 
 
 
 
 
 
 
 
609
  shutil.rmtree(tmp_gdrive_dir, ignore_errors=True)
610
- return output_path
 
 
 
 
 
 
 
 
 
 
611
 
612
  shutil.rmtree(tmp_gdrive_dir, ignore_errors=True)
613
-
614
  err_text = stderr.decode('utf-8', errors='ignore')
615
  raise RuntimeError(f"gdown failed with code {proc.returncode}: {err_text}")
616
 
@@ -992,14 +1014,17 @@ async def _run_leech_task(client, task: dict):
992
  if source_type == "telegram_file":
993
  await _download_telegram_file_with_progress(client, task["source_media_message"], temp_archive, task)
994
  else:
995
- await _download_url_with_progress(url, temp_archive, task)
 
 
996
  if task.get("cancelled"):
997
  raise asyncio.CancelledError()
998
 
999
  # Re-read display_name AFTER download - Google Drive download updates task["display_name"]
1000
  # with the real filename. Without this, the old URL-guessed name (e.g. "view") is used.
1001
  new_display = task.get("display_name") or display_name
1002
- if new_display != display_name:
 
1003
  # Physical rename so the file's extension is correct on disk
1004
  new_temp_archive = os.path.join(temp_dir, new_display)
1005
  if os.path.exists(temp_archive) and temp_archive != new_temp_archive:
@@ -1008,38 +1033,46 @@ async def _run_leech_task(client, task: dict):
1008
  temp_archive = new_temp_archive
1009
  display_name = new_display
1010
 
1011
- if _is_archive(temp_archive):
1012
- task["is_extracting"] = True
1013
- await _update_leech_status(task, "Extracting")
1014
- extracted_dir = os.path.join(temp_dir, "extracted")
1015
- os.makedirs(extracted_dir, exist_ok=True)
1016
- task["temp_paths"].append(extracted_dir)
1017
- await _extract_archive(temp_archive, extracted_dir, task)
 
 
 
 
 
 
 
1018
 
1019
  # Some releases ship a zip-inside-a-zip (or rar-inside-a-zip).
1020
  # Recursively extract any nested archives we find (max 2 levels deep)
1021
  # before scanning for media, so those aren't silently missed.
1022
- for _pass in range(2):
1023
- nested = []
1024
- for root, _, files in os.walk(extracted_dir):
1025
- for name in files:
1026
- path = os.path.join(root, name)
1027
- if _is_archive(path):
1028
- nested.append(path)
1029
- if not nested:
1030
- break
1031
- for nested_path in nested:
1032
- try:
1033
- nested_out = nested_path + "_extracted"
1034
- os.makedirs(nested_out, exist_ok=True)
1035
- await _extract_archive(nested_path, nested_out, task)
1036
- os.remove(nested_path)
1037
- except Exception as e:
1038
- logger.warning(f"Could not extract nested archive {nested_path}: {e}")
 
1039
 
1040
  media_files = []
1041
  all_extracted_files = []
1042
- for root, _, files in os.walk(extracted_dir):
1043
  for name in files:
1044
  path = os.path.join(root, name)
1045
  all_extracted_files.append(path)
@@ -1047,12 +1080,12 @@ async def _run_leech_task(client, task: dict):
1047
  media_files.append(path)
1048
  if not media_files:
1049
  logger.error(
1050
- f"No media files matched. Archive extracted {len(all_extracted_files)} file(s): "
1051
  f"{[os.path.basename(p) for p in all_extracted_files[:30]]}"
1052
  )
1053
  raise RuntimeError(
1054
- f"No supported media files found in archive "
1055
- f"({len(all_extracted_files)} file(s) extracted, none matched known media extensions)"
1056
  )
1057
  auto_merge = await codeflixbots.get_auto_merge(task["user_id"])
1058
  if auto_merge and len(media_files) > 1:
@@ -1067,10 +1100,7 @@ async def _run_leech_task(client, task: dict):
1067
  task["user_id"],
1068
  item,
1069
  file_name,
1070
- file_name, # BUGFIX: use this file's own name, not the archive's constant
1071
- # display_name - previously every episode in a zip was
1072
- # getting season/episode/quality extracted from the ZIP's
1073
- # filename, so all episodes ended up with the same output name.
1074
  task,
1075
  task["chat_id"],
1076
  task["status_message"],
 
577
 
578
  # ── Google Drive Intercept ──
579
  if "drive.google.com" in url:
580
+ is_folder = "/folders/" in url or "/drive/folders/" in url
581
  for attempt in range(1, DOWNLOAD_MAX_RETRIES + 1):
582
  try:
583
  task["retry_info"] = f"Initializing Google Drive (Attempt {attempt})" if attempt > 1 else None
584
+ status_label = "Downloading (GDrive Folder)" if is_folder else "Downloading (GDrive)"
585
+ await _update_leech_status(task, status_label, force=True)
586
 
587
  # Use gdown in a temporary directory to auto-extract the real filename
588
  tmp_gdrive_dir = output_path + "_gdrive_tmp"
589
  os.makedirs(tmp_gdrive_dir, exist_ok=True)
590
 
591
+ gdown_cmd = ["gdown"]
592
+ if is_folder:
593
+ gdown_cmd.append("--folder")
594
+ gdown_cmd.append(url)
595
+
596
  proc = await asyncio.create_subprocess_exec(
597
  *gdown_cmd, cwd=tmp_gdrive_dir, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
598
  )
 
603
  raise asyncio.CancelledError()
604
 
605
  if proc.returncode == 0:
606
+ if is_folder:
607
+ # Find all files recursively in tmp_gdrive_dir and move them to parent directory (temp_dir)
608
+ parent_dir = os.path.dirname(output_path)
609
+ moved_count = 0
610
+ for root, _, files_in_dir in os.walk(tmp_gdrive_dir):
611
+ for name in files_in_dir:
612
+ src_file = os.path.join(root, name)
613
+ dst_file = os.path.join(parent_dir, name)
614
+ # Avoid collision if the file exists
615
+ base, ext = os.path.splitext(name)
616
+ counter = 1
617
+ while os.path.exists(dst_file):
618
+ dst_file = os.path.join(parent_dir, f"{base}_{counter}{ext}")
619
+ counter += 1
620
+ shutil.move(src_file, dst_file)
621
+ moved_count += 1
622
  shutil.rmtree(tmp_gdrive_dir, ignore_errors=True)
623
+ if moved_count > 0:
624
+ return parent_dir
625
+ else:
626
+ downloaded_files = os.listdir(tmp_gdrive_dir)
627
+ if downloaded_files:
628
+ true_name = downloaded_files[0]
629
+ # Overwrite the placeholder "view" name with the real Google Drive filename
630
+ task["display_name"] = true_name
631
+ shutil.move(os.path.join(tmp_gdrive_dir, true_name), output_path)
632
+ shutil.rmtree(tmp_gdrive_dir, ignore_errors=True)
633
+ return output_path
634
 
635
  shutil.rmtree(tmp_gdrive_dir, ignore_errors=True)
 
636
  err_text = stderr.decode('utf-8', errors='ignore')
637
  raise RuntimeError(f"gdown failed with code {proc.returncode}: {err_text}")
638
 
 
1014
  if source_type == "telegram_file":
1015
  await _download_telegram_file_with_progress(client, task["source_media_message"], temp_archive, task)
1016
  else:
1017
+ download_result = await _download_url_with_progress(url, temp_archive, task)
1018
+ if download_result and os.path.isdir(download_result):
1019
+ temp_archive = download_result
1020
  if task.get("cancelled"):
1021
  raise asyncio.CancelledError()
1022
 
1023
  # Re-read display_name AFTER download - Google Drive download updates task["display_name"]
1024
  # with the real filename. Without this, the old URL-guessed name (e.g. "view") is used.
1025
  new_display = task.get("display_name") or display_name
1026
+ # Bypass rename if temp_archive is a directory
1027
+ if not os.path.isdir(temp_archive) and new_display != display_name:
1028
  # Physical rename so the file's extension is correct on disk
1029
  new_temp_archive = os.path.join(temp_dir, new_display)
1030
  if os.path.exists(temp_archive) and temp_archive != new_temp_archive:
 
1033
  temp_archive = new_temp_archive
1034
  display_name = new_display
1035
 
1036
+ is_archive_file = _is_archive(temp_archive) if os.path.isfile(temp_archive) else False
1037
+ is_directory_download = os.path.isdir(temp_archive)
1038
+
1039
+ if is_archive_file or is_directory_download:
1040
+ task["is_extracting"] = True if is_archive_file else False
1041
+ if is_archive_file:
1042
+ await _update_leech_status(task, "Extracting")
1043
+ extracted_dir = os.path.join(temp_dir, "extracted")
1044
+ os.makedirs(extracted_dir, exist_ok=True)
1045
+ task["temp_paths"].append(extracted_dir)
1046
+ await _extract_archive(temp_archive, extracted_dir, task)
1047
+ scan_dir = extracted_dir
1048
+ else:
1049
+ scan_dir = temp_archive
1050
 
1051
  # Some releases ship a zip-inside-a-zip (or rar-inside-a-zip).
1052
  # Recursively extract any nested archives we find (max 2 levels deep)
1053
  # before scanning for media, so those aren't silently missed.
1054
+ if is_archive_file:
1055
+ for _pass in range(2):
1056
+ nested = []
1057
+ for root, _, files in os.walk(scan_dir):
1058
+ for name in files:
1059
+ path = os.path.join(root, name)
1060
+ if _is_archive(path):
1061
+ nested.append(path)
1062
+ if not nested:
1063
+ break
1064
+ for nested_path in nested:
1065
+ try:
1066
+ nested_out = nested_path + "_extracted"
1067
+ os.makedirs(nested_out, exist_ok=True)
1068
+ await _extract_archive(nested_path, nested_out, task)
1069
+ os.remove(nested_path)
1070
+ except Exception as e:
1071
+ logger.warning(f"Could not extract nested archive {nested_path}: {e}")
1072
 
1073
  media_files = []
1074
  all_extracted_files = []
1075
+ for root, _, files in os.walk(scan_dir):
1076
  for name in files:
1077
  path = os.path.join(root, name)
1078
  all_extracted_files.append(path)
 
1080
  media_files.append(path)
1081
  if not media_files:
1082
  logger.error(
1083
+ f"No media files matched. Directory contains {len(all_extracted_files)} file(s): "
1084
  f"{[os.path.basename(p) for p in all_extracted_files[:30]]}"
1085
  )
1086
  raise RuntimeError(
1087
+ f"No supported media files found "
1088
+ f"({len(all_extracted_files)} file(s) found, none matched known media extensions)"
1089
  )
1090
  auto_merge = await codeflixbots.get_auto_merge(task["user_id"])
1091
  if auto_merge and len(media_files) > 1:
 
1100
  task["user_id"],
1101
  item,
1102
  file_name,
1103
+ file_name,
 
 
 
1104
  task,
1105
  task["chat_id"],
1106
  task["status_message"],