aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/conversion.py
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-07-08 02:13:35 +0200
committersalmonik <raisersoft@proton.me>2026-07-08 02:13:35 +0200
commitcd8ed8bc5793985df2c1692072a932ceee517b95 (patch)
tree42d18a4140259b5c68b4dd05b7fcb398f78b8d7f /cogs/conversion.py
parentMerge pull request 'Voice related fixes, alongside simplifying assistant' (#5... (diff)
downloadsenator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.tar.gz
senator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.zip
OPEN THE RUST GATES
Signed-off-by: salmonik <raisersoft@proton.me>
Diffstat (limited to 'cogs/conversion.py')
-rw-r--r--cogs/conversion.py245
1 files changed, 0 insertions, 245 deletions
diff --git a/cogs/conversion.py b/cogs/conversion.py
deleted file mode 100644
index f9e4c84..0000000
--- a/cogs/conversion.py
+++ /dev/null
@@ -1,245 +0,0 @@
1# This file is part of Senator Sperma
2# Licensed under the GPL-3.0 License
3# Copyright (C) 2026 Salmonik @ salmonik@tuta.io
4# https://codeberg.org/salmonik/senator-sperma
5
6import discord
7import requests
8from discord.ext import commands
9import logging
10import os
11import asyncio
12import aiofiles
13import mimetypes
14import subprocess
15import urllib.parse
16import shlex
17from typing import Optional
18from discord import AutocompleteContext
19from cogs.utils import file_search as search_utils
20
21logger = logging.getLogger(__name__)
22
23def filetype(filename):
24 mime_type, _ = mimetypes.guess_type(filename)
25 if mime_type:
26 if mime_type.startswith('video/'):
27 return "Video"
28 elif mime_type.startswith('audio/'):
29 return "Audio"
30 elif mime_type.startswith('image/'):
31 return "Image"
32 return "Unknown"
33
34class Conversion(commands.Cog):
35 def __init__(self, bot):
36 self.bot = bot
37 from cogs.utils.temp import get_temp_dir
38 from cogs.utils.settings import get_baseurl, get_uploadurl
39 self.temp_dir = get_temp_dir()
40 self._cached_urls = {}
41 self._baseurl = get_baseurl()
42 self._uploadurl = get_uploadurl()
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 filename = urllib.parse.unquote(filename)
67 file_path = os.path.join(self.temp_dir, filename)
68
69 async with aiofiles.open(file_path, 'wb') as f:
70 await f.write(response.content)
71 return file_path
72 except Exception as e:
73 logger.error(f"Error downloading from {url}: {e}")
74 return None
75
76 async def _setup_file_paths(self, plik, output_format):
77 """Setup input and output file paths"""
78 from cogs.utils.temp import get_temp_dir
79
80 temp_dir = get_temp_dir()
81 file_path = os.path.join(temp_dir, plik.filename)
82 filename_noext = os.path.splitext(plik.filename)[0]
83 await plik.save(file_path)
84
85 input_path = file_path
86 output_path = os.path.join(temp_dir, f"{filename_noext}.{output_format}")
87 return input_path, output_path
88
89 def _build_conversion_command(self, file_type, input_path, output_path, args):
90 """Build the appropriate conversion command based on file type"""
91 if file_type in ["Video", "Audio"]:
92 return f"ffmpeg -i {shlex.quote(input_path)} {args} {shlex.quote(output_path)}"
93 elif file_type == "Image":
94 return f"magick {shlex.quote(input_path)} {args} {shlex.quote(output_path)}"
95 else:
96 return None
97
98 async def _upload_to_copyparty(self, output_path):
99 """Upload file to copyparty and return download URL"""
100 try:
101 async with aiofiles.open(output_path, 'rb') as f:
102 file_content = await f.read()
103
104 files = {
105 'f': (os.path.basename(output_path), file_content, 'application/octet-stream')
106 }
107 headers = {
108 'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}'
109 }
110 params = {
111 'j': '1',
112 'ck': 'b2'
113 }
114
115 response = requests.post(self._uploadurl, files=files, headers=headers, params=params)
116
117 if response.status_code in [200, 201]:
118 result = response.json()
119 if 'fileurl' in result:
120 return result['fileurl']
121 else:
122 raise ValueError(f"Server response missing fileurl: {result}")
123 else:
124 error_msg = response.text if response.text else 'Unknown error'
125 raise requests.RequestException(f"HTTP {response.status_code}: {error_msg}")
126
127 except Exception as e:
128 logger.error(f"copyparty upload error: {e}", exc_info=True)
129 raise
130
131 async def _handle_file_output(self, ctx, output_path):
132 """Handle file output based on size"""
133 if os.path.getsize(output_path) > 1024*1024*10:
134 try:
135 dl_url = await self._upload_to_copyparty(output_path)
136 await ctx.followup.send(f"wysyłon\n{dl_url}\n")
137 except Exception as upload_error:
138 await ctx.followup.send(f"copyparty mnie nienawidzi :cccc\n {str(upload_error)}")
139 else:
140 await ctx.respond(file=discord.File(output_path))
141
142 def _cleanup_files(self, *file_paths):
143 """Clean up temporary files"""
144 for path in file_paths:
145 if os.path.exists(path):
146 os.remove(path)
147
148 @commands.slash_command(name="konwersja", description="konwertuj plik na inny format")
149 @discord.option(
150 name="plik",
151 description="plik z copyparty",
152 required=False,
153 autocomplete=_file_autocomplete
154 )
155 @discord.option(
156 name="zalacznik",
157 description="plik z discorda",
158 required=False,
159 type=discord.Attachment
160 )
161 @discord.option(
162 name="serwer?",
163 description="wysłać plik na serwer?",
164 required=False,
165 type=bool,
166 default=False
167 )
168 @discord.option(
169 name="format",
170 description="format konwersji",
171 required=True,
172 type=str
173 )
174 async def convert(self, ctx, plik: str = None, zalacznik: discord.Attachment = None, server: bool = False, output_format: str = None):
175 await ctx.defer()
176
177 input_path = None
178 original_filename = None
179
180 try:
181 if plik:
182 url = self._cached_urls.get(ctx.author.id, {}).get(plik)
183 if not url:
184 await ctx.respond("Wybierz plik z podpowiedzi", ephemeral=True)
185 return
186 input_path = await self._download_from_url(url)
187 original_filename = plik
188 elif zalacznik:
189 input_path = os.path.join(self.temp_dir, zalacznik.filename)
190 original_filename = zalacznik.filename
191 await zalacznik.save(input_path)
192 else:
193 await ctx.respond("Podaj plik z copyparty lub załącznik", ephemeral=True)
194 return
195
196 if not input_path or not os.path.exists(input_path):
197 await ctx.respond("Nie udało się pobrać pliku", ephemeral=True)
198 return
199
200 output_path = os.path.join(self.temp_dir, f"{os.path.splitext(original_filename)[0]}.{output_format}")
201
202 file_type = filetype(input_path)
203 logger.info(f"Conversion started for: {file_type}")
204
205 command = self._build_conversion_command(file_type, input_path, output_path, "")
206 logger.info(f"Running: {command}")
207 if not command:
208 await ctx.respond("Plik jest nieobsługiwany", ephemeral=True)
209 return
210
211 proc = await asyncio.create_subprocess_exec(
212 *shlex.split(command),
213 stdout=asyncio.subprocess.PIPE,
214 stderr=asyncio.subprocess.PIPE
215 )
216 stdout, stderr = await proc.communicate()
217
218 if proc.returncode != 0:
219 logger.error(f"FFmpeg error: {stderr.decode() if stderr else 'unknown'}")
220 await ctx.respond(f"Błąd konwersji: {stderr.decode() if stderr else 'unknown'}", ephemeral=True)
221 return
222
223 if os.path.getsize(output_path) > 1024 * 1024 * 10 or server:
224 dl_url = await self._upload_to_copyparty(output_path)
225 if dl_url:
226 await ctx.respond(f"Skonwertowano\n{dl_url}")
227 else:
228 await ctx.respond("Nie udało się wysłać na serwer", ephemeral=True)
229 else:
230 await ctx.respond(file=discord.File(output_path, filename=f"{original_filename}"))
231
232 logger.info(f"Conversion completed for: {file_type}")
233
234 except subprocess.CalledProcessError as e:
235 await ctx.respond(f"Conversion failed: {str(e)}", ephemeral=True)
236 except Exception as e:
237 await ctx.respond(f"An error occurred: {str(e)}", ephemeral=True)
238 finally:
239 if input_path and os.path.exists(input_path):
240 os.remove(input_path)
241 if 'output_path' in locals() and os.path.exists(output_path):
242 os.remove(output_path)
243
244def setup(bot):
245 bot.add_cog(Conversion(bot))