diff options
Diffstat (limited to '')
| -rw-r--r-- | cogs/downloader.py | 253 |
1 files changed, 123 insertions, 130 deletions
diff --git a/cogs/downloader.py b/cogs/downloader.py index 0331ae0..d2835d8 100644 --- a/cogs/downloader.py +++ b/cogs/downloader.py | |||
| @@ -12,6 +12,7 @@ import re | |||
| 12 | import yt_dlp | 12 | import yt_dlp |
| 13 | from discord.ext import commands | 13 | from discord.ext import commands |
| 14 | from dotenv import load_dotenv | 14 | from dotenv import load_dotenv |
| 15 | import aiofiles | ||
| 15 | 16 | ||
| 16 | logger = logging.getLogger(__name__) | 17 | logger = logging.getLogger(__name__) |
| 17 | 18 | ||
| @@ -68,152 +69,144 @@ class Downloader(commands.Cog): | |||
| 68 | if 'details' in response: | 69 | if 'details' in response: |
| 69 | logger.error(f"Details: {response['details']}") | 70 | logger.error(f"Details: {response['details']}") |
| 70 | 71 | ||
| 71 | |||
| 72 | @staticmethod | 72 | @staticmethod |
| 73 | async def download_standalone(url: str, catbox: bool = False, fformat: str = "mp4", filename_user: str = None): | 73 | async def download_standalone(url: str, catbox: bool = False, fformat: str = "mp4", filename_user: str = None): |
| 74 | try: | 74 | try: |
| 75 | logger.info(f"Downloading {url} as {fformat}") | 75 | logger.info(f"Downloading {url} as {fformat}") |
| 76 | 76 | ||
| 77 | with tempfile.TemporaryDirectory(delete=False) as temp_dir: | 77 | with tempfile.TemporaryDirectory(delete=False) as temp_dir: |
| 78 | # Configure yt-dlp to use the original filename | 78 | ydl_opts = Downloader._get_ydl_options(fformat, temp_dir) |
| 79 | ydl_opts = { | ||
| 80 | 'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'), | ||
| 81 | 'format': 'bestaudio/best' if fformat in ['mp3', 'wav', | ||
| 82 | 'm4a'] else 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', | ||
| 83 | 'merge_output_format': 'mp4', | ||
| 84 | 'postprocessors': [], | ||
| 85 | 'remote-components': 'ejs:github', | ||
| 86 | 'js-runtimes': {'deno': {'path': '/usr/bin/deno'}}, | ||
| 87 | 'o': '"%(title)s.%(ext)s"' | ||
| 88 | } | ||
| 89 | |||
| 90 | # Add post-processing for audio formats | ||
| 91 | if fformat in ['mp3', 'wav', 'm4a']: | ||
| 92 | ydl_opts['postprocessors'].append({ | ||
| 93 | 'key': 'FFmpegExtractAudio', | ||
| 94 | 'preferredcodec': fformat, | ||
| 95 | 'preferredquality': '192', | ||
| 96 | }) | ||
| 97 | elif fformat != 'mp4': # For other video formats | ||
| 98 | ydl_opts['postprocessors'].append({ | ||
| 99 | 'key': 'FFmpegVideoConvertor', | ||
| 100 | 'preferedformat': fformat, | ||
| 101 | }) | ||
| 102 | 79 | ||
| 103 | with yt_dlp.YoutubeDL(ydl_opts) as ydl: | 80 | with yt_dlp.YoutubeDL(ydl_opts) as ydl: |
| 104 | ydl.download([url]) | 81 | ydl.download([url]) |
| 105 | 82 | ||
| 106 | # Find the downloaded file (should be the only file in the temp directory) | 83 | actual_file, filename = Downloader._find_and_prepare_file(temp_dir, filename_user) |
| 107 | files = [f for f in os.listdir(temp_dir) if os.path.isfile(os.path.join(temp_dir, f))] | ||
| 108 | if not files: | ||
| 109 | raise Exception(f"No file was downloaded") | ||
| 110 | |||
| 111 | actual_file = os.path.join(temp_dir, files[0]) | ||
| 112 | extension = actual_file.split('.')[-1] | ||
| 113 | 84 | ||
| 114 | if not filename_user: | 85 | if Downloader._should_upload_to_server(actual_file, catbox): |
| 115 | filename = os.path.basename(actual_file) | 86 | return await Downloader._upload_to_server(actual_file, filename, temp_dir) |
| 116 | else: | 87 | else: |
| 117 | filename = filename_user + '.' + extension | 88 | return Downloader._return_file_response(actual_file, filename, temp_dir) |
| 118 | actual_file = os.path.join(temp_dir, filename) | 89 | |
| 119 | 90 | except Exception as e: | |
| 120 | if not os.path.exists(actual_file): | 91 | return Downloader._create_error_response("Download failed", str(e), temp_dir if 'temp_dir' in locals() else None) |
| 121 | raise Exception(f"Downloaded file not found: {actual_file}") | 92 | |
| 122 | 93 | @staticmethod | |
| 123 | if os.path.getsize(actual_file) > 1024*1024*10 or catbox: | 94 | def _get_ydl_options(fformat: str, temp_dir: str) -> dict: |
| 124 | try: | 95 | ydl_opts = { |
| 125 | 96 | 'outtmpl': os.path.join(temp_dir, '%(title)s.%(ext)s'), | |
| 126 | # Upload to Copyparty | 97 | 'format': 'bestaudio/best' if fformat in ['mp3', 'wav', 'm4a'] else 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best', |
| 127 | with open(actual_file, 'rb') as f: | 98 | 'merge_output_format': 'mp4', |
| 128 | files = { | 99 | 'postprocessors': [], |
| 129 | 'f': (filename, f, 'application/octet-stream') | 100 | 'remote-components': 'ejs:github', |
| 130 | } | 101 | 'js-runtimes': {'deno': {'path': '/usr/bin/deno'}}, |
| 131 | headers = { | 102 | 'o': '"%(title)s.%(ext)s"' |
| 132 | 'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}' | 103 | } |
| 133 | } | 104 | |
| 134 | params = { | 105 | if fformat in ['mp3', 'wav', 'm4a']: |
| 135 | 'j': '1', # Return JSON response | 106 | ydl_opts['postprocessors'].append({ |
| 136 | 'ck': 'b2' # Use blake2b checksum | 107 | 'key': 'FFmpegExtractAudio', |
| 137 | } | 108 | 'preferredcodec': fformat, |
| 138 | 109 | 'preferredquality': '192', | |
| 139 | # Ensure the URL ends with a single slash | 110 | }) |
| 140 | base_url = "https://catbox.2137697.xyz/sperma/" | 111 | elif fformat != 'mp4': |
| 141 | 112 | ydl_opts['postprocessors'].append({ | |
| 142 | response = requests.post( | 113 | 'key': 'FFmpegVideoConvertor', |
| 143 | base_url, | 114 | 'preferedformat': fformat, |
| 144 | files=files, | 115 | }) |
| 145 | headers=headers, | 116 | |
| 146 | params=params | 117 | return ydl_opts |
| 147 | ) | 118 | |
| 148 | 119 | @staticmethod | |
| 149 | if response.status_code == 200 or response.status_code == 201: | 120 | def _find_and_prepare_file(temp_dir: str, filename_user: str) -> tuple: |
| 150 | try: | 121 | files = [f for f in os.listdir(temp_dir) if os.path.isfile(os.path.join(temp_dir, f))] |
| 151 | result = response.json() | 122 | if not files: |
| 152 | if 'fileurl' in result: | 123 | raise FileNotFoundError("No file was downloaded") |
| 153 | dl_url = result['fileurl'] | 124 | |
| 154 | return { | 125 | actual_file = os.path.join(temp_dir, files[0]) |
| 155 | "status": "success", | 126 | extension = actual_file.split('.')[-1] |
| 156 | "type": "url", | 127 | |
| 157 | "content": dl_url, | 128 | if not filename_user: |
| 158 | "filename": filename, | 129 | filename = os.path.basename(actual_file) |
| 159 | "temp_dir": temp_dir | 130 | else: |
| 160 | } | 131 | filename = filename_user + '.' + extension |
| 161 | else: | 132 | actual_file = os.path.join(temp_dir, filename) |
| 162 | return { | 133 | |
| 163 | "status": "error", | 134 | if not os.path.exists(actual_file): |
| 164 | "type": "error", | 135 | raise FileNotFoundError(f"Downloaded file not found: {actual_file}") |
| 165 | "content": "Invalid response from file server", | 136 | |
| 166 | "details": str(result), | 137 | return actual_file, filename |
| 167 | "temp_dir": temp_dir | ||
| 168 | } | ||
| 169 | except ValueError: | ||
| 170 | return { | ||
| 171 | "status": "error", | ||
| 172 | "type": "error", | ||
| 173 | "content": "Failed to parse server response", | ||
| 174 | "details": response.text[:200], # First 200 chars of response | ||
| 175 | "temp_dir": temp_dir | ||
| 176 | } | ||
| 177 | else: | ||
| 178 | error_msg = response.text if response.text else 'Unknown error' | ||
| 179 | return { | ||
| 180 | "status": "error", | ||
| 181 | "type": "error", | ||
| 182 | "content": f"Server returned {response.status_code}", | ||
| 183 | "details": error_msg, | ||
| 184 | "temp_dir": temp_dir | ||
| 185 | } | ||
| 186 | |||
| 187 | except Exception as upload_error: | ||
| 188 | return { | ||
| 189 | "status": "error", | ||
| 190 | "type": "error", | ||
| 191 | "content": "Upload failed", | ||
| 192 | "details": str(upload_error), | ||
| 193 | "temp_dir": temp_dir | ||
| 194 | } | ||
| 195 | 138 | ||
| 139 | @staticmethod | ||
| 140 | def _should_upload_to_server(actual_file: str, catbox: bool) -> bool: | ||
| 141 | return os.path.getsize(actual_file) > 1024*1024*10 or catbox | ||
| 142 | |||
| 143 | @staticmethod | ||
| 144 | async def _upload_to_server(actual_file: str, filename: str, temp_dir: str) -> dict: | ||
| 145 | try: | ||
| 146 | async with aiofiles.open(actual_file, 'rb') as f: | ||
| 147 | content = await f.read() | ||
| 148 | files = {'f': (filename, content, 'application/octet-stream')} | ||
| 149 | headers = {'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}'} | ||
| 150 | params = {'j': '1', 'ck': 'b2'} | ||
| 151 | base_url = "https://catbox.2137697.xyz/sperma/" | ||
| 152 | |||
| 153 | response = requests.post(base_url, files=files, headers=headers, params=params) | ||
| 154 | |||
| 155 | if response.status_code in [200, 201]: | ||
| 156 | return Downloader._handle_upload_success(response, filename, temp_dir) | ||
| 196 | else: | 157 | else: |
| 197 | # If we have a file to return directly | 158 | return Downloader._create_error_response( |
| 198 | return { | 159 | f"Server returned {response.status_code}", |
| 199 | "status": "success", | 160 | response.text or 'Unknown error', |
| 200 | "type": "file", | 161 | temp_dir |
| 201 | "content": actual_file, | 162 | ) |
| 202 | "filename": filename, | 163 | except Exception as upload_error: |
| 203 | "temp_dir": temp_dir | 164 | return Downloader._create_error_response("Upload failed", str(upload_error), temp_dir) |
| 204 | } | ||
| 205 | 165 | ||
| 206 | except Exception as e: | 166 | @staticmethod |
| 207 | error_msg = f"Error in download command: {e}" | 167 | def _handle_upload_success(response: requests.Response, filename: str, temp_dir: str) -> dict: |
| 208 | logger.error(error_msg, exc_info=True) | 168 | try: |
| 209 | return { | 169 | result = response.json() |
| 210 | "status": "error", | 170 | if 'fileurl' in result: |
| 211 | "type": "error", | 171 | return { |
| 212 | "content": "Download failed", | 172 | "status": "success", |
| 213 | "details": str(e), | 173 | "type": "url", |
| 214 | "temp_dir": temp_dir | 174 | "content": result['fileurl'], |
| 215 | } | 175 | "filename": filename, |
| 176 | "temp_dir": temp_dir | ||
| 177 | } | ||
| 178 | else: | ||
| 179 | return Downloader._create_error_response( | ||
| 180 | "Invalid response from file server", | ||
| 181 | str(result), | ||
| 182 | temp_dir | ||
| 183 | ) | ||
| 184 | except ValueError: | ||
| 185 | return Downloader._create_error_response( | ||
| 186 | "Failed to parse server response", | ||
| 187 | response.text[:200], | ||
| 188 | temp_dir | ||
| 189 | ) | ||
| 216 | 190 | ||
| 191 | @staticmethod | ||
| 192 | def _return_file_response(actual_file: str, filename: str, temp_dir: str) -> dict: | ||
| 193 | return { | ||
| 194 | "status": "success", | ||
| 195 | "type": "file", | ||
| 196 | "content": actual_file, | ||
| 197 | "filename": filename, | ||
| 198 | "temp_dir": temp_dir | ||
| 199 | } | ||
| 200 | |||
| 201 | @staticmethod | ||
| 202 | def _create_error_response(content: str, details: str, temp_dir: str) -> dict: | ||
| 203 | return { | ||
| 204 | "status": "error", | ||
| 205 | "type": "error", | ||
| 206 | "content": content, | ||
| 207 | "details": details, | ||
| 208 | "temp_dir": temp_dir | ||
| 209 | } | ||
| 217 | 210 | ||
| 218 | 211 | ||
| 219 | def setup(bot): | 212 | def setup(bot): |
