aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/downloader.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--cogs/downloader.py253
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
12import yt_dlp 12import yt_dlp
13from discord.ext import commands 13from discord.ext import commands
14from dotenv import load_dotenv 14from dotenv import load_dotenv
15import aiofiles
15 16
16logger = logging.getLogger(__name__) 17logger = 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
219def setup(bot): 212def setup(bot):