aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/conversion.py
diff options
context:
space:
mode:
Diffstat (limited to 'cogs/conversion.py')
-rw-r--r--cogs/conversion.py198
1 files changed, 147 insertions, 51 deletions
diff --git a/cogs/conversion.py b/cogs/conversion.py
index f61d30f..1117caf 100644
--- a/cogs/conversion.py
+++ b/cogs/conversion.py
@@ -12,6 +12,11 @@ import asyncio
12import aiofiles 12import aiofiles
13import mimetypes 13import mimetypes
14import subprocess 14import subprocess
15import urllib.parse
16import shlex
17from typing import Optional
18from discord import AutocompleteContext
19from cogs.utils import file_search as search_utils
15 20
16logger = logging.getLogger(__name__) 21logger = logging.getLogger(__name__)
17 22
@@ -29,28 +34,63 @@ def filetype(filename):
29class Conversion(commands.Cog): 34class Conversion(commands.Cog):
30 def __init__(self, bot): 35 def __init__(self, bot):
31 self.bot = bot 36 self.bot = bot
37 from cogs.utils.temp import get_temp_dir
38 from cogs.utils.settings import get_baseurl
39 self.temp_dir = get_temp_dir()
40 self._cached_urls = {}
41 self._baseurl = os.getenv("COPYPARTY_URL") or get_baseurl()
42 self._uploadurl = os.getenv("COPYPARTY_URL_UPLOAD")
43
44 async def _file_autocomplete(self, ctx: AutocompleteContext):
45 query = ctx.value.lower() if ctx.value else ""
46 results = await search_utils.UniversalSearcher.search_copyparty_autocomplete(query, max_results=25)
47
48 user_id = ctx.interaction.user.id
49 self._cached_urls[user_id] = {urllib.parse.unquote(url.split('/')[-1]): url for url in results}
50
51 choices = []
52 for url in results[:25]:
53 filename = urllib.parse.unquote(url.split('/')[-1])
54 display_name = filename[:99] + "…" if len(filename) > 100 else filename
55 choices.append(discord.OptionChoice(name=display_name, value=filename))
56 return choices
57
58 async def _download_from_url(self, url: str) -> Optional[str]:
59 """Download file from copyparty URL and return local path"""
60 try:
61 response = requests.get(url)
62 if response.status_code != 200:
63 return None
64
65 filename = url.split('/')[-1].split('?')[0]
66 file_path = os.path.join(self.temp_dir, filename)
67
68 async with aiofiles.open(file_path, 'wb') as f:
69 await f.write(response.content)
70 return file_path
71 except Exception as e:
72 logger.error(f"Error downloading from {url}: {e}")
73 return None
32 74
33 async def _setup_file_paths(self, plik, output_format): 75 async def _setup_file_paths(self, plik, output_format):
34 """Setup input and output file paths""" 76 """Setup input and output file paths"""
35 bot_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 77 from cogs.utils.temp import get_temp_dir
36 temp_dir = os.path.join(bot_dir, "temp") 78
37 if not os.path.exists(temp_dir): 79 temp_dir = get_temp_dir()
38 os.makedirs(temp_dir)
39
40 file_path = os.path.join(temp_dir, plik.filename) 80 file_path = os.path.join(temp_dir, plik.filename)
41 filename_noext = os.path.splitext(plik.filename)[0] 81 filename_noext = os.path.splitext(plik.filename)[0]
42 await plik.save(file_path) 82 await plik.save(file_path)
43 83
44 input_path = file_path 84 input_path = file_path
45 output_path = os.path.join(temp_dir, f"{filename_noext}k.{output_format}") 85 output_path = os.path.join(temp_dir, f"{filename_noext}.{output_format}")
46 return input_path, output_path 86 return input_path, output_path
47 87
48 def _build_conversion_command(self, file_type, input_path, output_path, args): 88 def _build_conversion_command(self, file_type, input_path, output_path, args):
49 """Build the appropriate conversion command based on file type""" 89 """Build the appropriate conversion command based on file type"""
50 if file_type in ["Video", "Audio"]: 90 if file_type in ["Video", "Audio"]:
51 return f"ffmpeg -i {input_path} {args} {output_path}" 91 return f"ffmpeg -i {shlex.quote(input_path)} {args} {shlex.quote(output_path)}"
52 elif file_type == "Image": 92 elif file_type == "Image":
53 return f"magick {input_path} {args} {output_path}" 93 return f"magick {shlex.quote(input_path)} {args} {shlex.quote(output_path)}"
54 else: 94 else:
55 return None 95 return None
56 96
@@ -58,30 +98,31 @@ class Conversion(commands.Cog):
58 """Upload file to copyparty and return download URL""" 98 """Upload file to copyparty and return download URL"""
59 try: 99 try:
60 async with aiofiles.open(output_path, 'rb') as f: 100 async with aiofiles.open(output_path, 'rb') as f:
61 files = { 101 file_content = await f.read()
62 'f': (output_path, f, 'application/octet-stream') 102
63 } 103 files = {
64 headers = { 104 'f': (os.path.basename(output_path), file_content, 'application/octet-stream')
65 'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}' 105 }
66 } 106 headers = {
67 params = { 107 'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}'
68 'j': '1', 108 }
69 'ck': 'b2' 109 params = {
70 } 110 'j': '1',
71 111 'ck': 'b2'
72 base_url = "https://catbox.2137697.xyz/sperma/" 112 }
73 response = requests.post(base_url, files=files, headers=headers, params=params) 113
74 114 response = requests.post(self._uploadurl, files=files, headers=headers, params=params)
75 if response.status_code in [200, 201]: 115
76 result = response.json() 116 if response.status_code in [200, 201]:
77 if 'fileurl' in result: 117 result = response.json()
78 return result['fileurl'] 118 if 'fileurl' in result:
79 else: 119 return result['fileurl']
80 raise ValueError(f"Server response missing fileurl: {result}")
81 else: 120 else:
82 error_msg = response.text if response.text else 'Unknown error' 121 raise ValueError(f"Server response missing fileurl: {result}")
83 raise requests.RequestException(f"HTTP {response.status_code}: {error_msg}") 122 else:
84 123 error_msg = response.text if response.text else 'Unknown error'
124 raise requests.RequestException(f"HTTP {response.status_code}: {error_msg}")
125
85 except Exception as e: 126 except Exception as e:
86 logger.error(f"copyparty upload error: {e}", exc_info=True) 127 logger.error(f"copyparty upload error: {e}", exc_info=True)
87 raise 128 raise
@@ -105,44 +146,99 @@ class Conversion(commands.Cog):
105 146
106 @commands.slash_command(name="konwersja", description="konwertuj plik na inny format") 147 @commands.slash_command(name="konwersja", description="konwertuj plik na inny format")
107 @discord.option( 148 @discord.option(
108 "plik", 149 name="plik",
109 description="plik do konwersji", 150 description="plik z copyparty",
110 required=True, 151 required=False,
152 autocomplete=_file_autocomplete
153 )
154 @discord.option(
155 name="zalacznik",
156 description="plik z discorda",
157 required=False,
111 type=discord.Attachment 158 type=discord.Attachment
112 ) 159 )
113 @discord.option( 160 @discord.option(
114 "format", 161 name="serwer?",
162 description="wysłać plik na serwer?",
163 required=False,
164 type=bool,
165 default=False
166 )
167 @discord.option(
168 name="format",
115 description="format konwersji", 169 description="format konwersji",
116 required=True, 170 required=True,
117 type=str 171 type=str
118 ) 172 )
119 async def convert(self, ctx, plik, output_format, args): 173 async def convert(self, ctx, plik: str = None, zalacznik: discord.Attachment = None, server: bool = False, output_format: str = None):
120 await ctx.defer() 174 await ctx.defer()
121 175
176 input_path = None
177 original_filename = None
178
122 try: 179 try:
123 input_path, output_path = await self._setup_file_paths(plik, output_format) 180 if plik:
181 url = self._cached_urls.get(ctx.author.id, {}).get(plik)
182 if not url:
183 await ctx.respond("Wybierz plik z podpowiedzi", ephemeral=True)
184 return
185 input_path = await self._download_from_url(url)
186 original_filename = plik
187 elif zalacznik:
188 input_path = os.path.join(self.temp_dir, zalacznik.filename)
189 original_filename = zalacznik.filename
190 await zalacznik.save(input_path)
191 else:
192 await ctx.respond("Podaj plik z copyparty lub załącznik", ephemeral=True)
193 return
194
195 if not input_path or not os.path.exists(input_path):
196 await ctx.respond("Nie udało się pobrać pliku", ephemeral=True)
197 return
198
199 output_path = os.path.join(self.temp_dir, f"{os.path.splitext(original_filename)[0]}.{output_format}")
200
124 file_type = filetype(input_path) 201 file_type = filetype(input_path)
125 logger.info(f"Conversion started for: {file_type}") 202 logger.info(f"Conversion started for: {file_type}")
126 203
127 command = self._build_conversion_command(file_type, input_path, output_path, args) 204 command = self._build_conversion_command(file_type, input_path, output_path, "")
205 logger.info(f"Running: {command}")
128 if not command: 206 if not command:
129 await ctx.respond("Plik jest nieobsługiwany", ephemeral=True) 207 await ctx.respond("Plik jest nieobsługiwany", ephemeral=True)
130 logger.info("Unsupported file type")
131 return 208 return
132 209
133 proc = await asyncio.create_subprocess_exec(*command.split()) 210 proc = await asyncio.create_subprocess_exec(
134 await proc.wait() 211 *shlex.split(command),
135 212 stdout=asyncio.subprocess.PIPE,
136 await self._handle_file_output(ctx, output_path) 213 stderr=asyncio.subprocess.PIPE
214 )
215 stdout, stderr = await proc.communicate()
216
217 if proc.returncode != 0:
218 logger.error(f"FFmpeg error: {stderr.decode() if stderr else 'unknown'}")
219 await ctx.respond(f"Błąd konwersji: {stderr.decode() if stderr else 'unknown'}", ephemeral=True)
220 return
221
222 if os.path.getsize(output_path) > 1024 * 1024 * 10 or server:
223 dl_url = await self._upload_to_copyparty(output_path)
224 if dl_url:
225 await ctx.respond(f"Skonwertowano\n{dl_url}")
226 else:
227 await ctx.respond("Nie udało się wysłać na serwer", ephemeral=True)
228 else:
229 await ctx.respond(file=discord.File(output_path, filename=f"{original_filename}"))
230
137 logger.info(f"Conversion completed for: {file_type}") 231 logger.info(f"Conversion completed for: {file_type}")
138 232
139 except subprocess.CalledProcessError as e: 233 except subprocess.CalledProcessError as e:
140 await ctx.respond(f"Conversion failed: {str(e)}", ephemeral=True) 234 await ctx.respond(f"Conversion failed: {str(e)}", ephemeral=True)
141 except Exception as e: 235 except Exception as e:
142 await ctx.respond(f"An error occurred: {str(e)}", ephemeral=True) 236 await ctx.respond(f"An error occurred: {str(e)}", ephemeral=True)
143 finally: 237 finally:
144 if 'input_path' in locals(): 238 if input_path and os.path.exists(input_path):
145 self._cleanup_files(input_path, output_path) 239 os.remove(input_path)
240 if 'output_path' in locals() and os.path.exists(output_path):
241 os.remove(output_path)
146 242
147def setup(bot): 243def setup(bot):
148 bot.add_cog(Conversion(bot)) \ No newline at end of file 244 bot.add_cog(Conversion(bot))