diff options
Diffstat (limited to '')
| -rw-r--r-- | cogs/uploader.py | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/cogs/uploader.py b/cogs/uploader.py new file mode 100644 index 0000000..68a3938 --- /dev/null +++ b/cogs/uploader.py | |||
| @@ -0,0 +1,154 @@ | |||
| 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 | |||
| 6 | import os | ||
| 7 | import aiofiles | ||
| 8 | import requests | ||
| 9 | import urllib.parse | ||
| 10 | from typing import List, Optional | ||
| 11 | import discord | ||
| 12 | from discord.ext import commands | ||
| 13 | from discord import AutocompleteContext | ||
| 14 | from cogs.utils import file_search as search_utils | ||
| 15 | import logging | ||
| 16 | |||
| 17 | logger = logging.getLogger(__name__) | ||
| 18 | |||
| 19 | |||
| 20 | class Uploader(commands.Cog): | ||
| 21 | def __init__(self, bot): | ||
| 22 | from cogs.utils.temp import get_temp_dir | ||
| 23 | self.bot = bot | ||
| 24 | self.temp_dir = get_temp_dir() | ||
| 25 | self._cached_urls = {} | ||
| 26 | self._uploadurl = os.getenv("COPYPARTY_URL_UPLOAD") | ||
| 27 | |||
| 28 | async def _file_autocomplete(self, ctx: AutocompleteContext): | ||
| 29 | query = ctx.value.lower() if ctx.value else "" | ||
| 30 | results = await search_utils.UniversalSearcher.search_copyparty_autocomplete(query, max_results=25) | ||
| 31 | |||
| 32 | user_id = ctx.interaction.user.id | ||
| 33 | self._cached_urls[user_id] = {urllib.parse.unquote(url.split('/')[-1]): url for url in results} | ||
| 34 | |||
| 35 | choices = [] | ||
| 36 | for url in results[:25]: | ||
| 37 | filename = urllib.parse.unquote(url.split('/')[-1]) | ||
| 38 | display_name = filename[:99] + "…" if len(filename) > 100 else filename | ||
| 39 | choices.append(discord.OptionChoice(name=display_name, value=filename)) | ||
| 40 | return choices | ||
| 41 | |||
| 42 | async def _download_from_url(self, url: str) -> Optional[str]: | ||
| 43 | """Download file from copyparty URL and return local path""" | ||
| 44 | try: | ||
| 45 | response = requests.get(url) | ||
| 46 | if response.status_code != 200: | ||
| 47 | return None | ||
| 48 | |||
| 49 | filename = url.split('/')[-1].split('?')[0] | ||
| 50 | file_path = os.path.join(self.temp_dir, filename) | ||
| 51 | |||
| 52 | async with aiofiles.open(file_path, 'wb') as f: | ||
| 53 | await f.write(response.content) | ||
| 54 | return file_path | ||
| 55 | except Exception as e: | ||
| 56 | logger.error(f"Error downloading from {url}: {e}") | ||
| 57 | return None | ||
| 58 | |||
| 59 | async def _upload_to_copyparty(self, file_path: str) -> Optional[str]: | ||
| 60 | """Upload file to copyparty and return download URL""" | ||
| 61 | try: | ||
| 62 | async with aiofiles.open(file_path, 'rb') as f: | ||
| 63 | file_content = await f.read() | ||
| 64 | |||
| 65 | files = {'f': (os.path.basename(file_path), file_content, 'application/octet-stream')} | ||
| 66 | headers = {'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}'} | ||
| 67 | params = {'j': '1', 'ck': 'b2'} | ||
| 68 | |||
| 69 | response = requests.post(self._uploadurl, files=files, headers=headers, params=params) | ||
| 70 | |||
| 71 | if response.status_code in [200, 201]: | ||
| 72 | result = response.json() | ||
| 73 | return result.get('fileurl') | ||
| 74 | except Exception as e: | ||
| 75 | logger.error(f"Upload error: {e}") | ||
| 76 | return None | ||
| 77 | |||
| 78 | async def _upload_file(self, file_path: str) -> Optional[str]: | ||
| 79 | """Upload a file and return the URL""" | ||
| 80 | if not self._uploadurl: | ||
| 81 | logger.error("COPYPARTY_URL_UPLOAD not set!") | ||
| 82 | return None | ||
| 83 | |||
| 84 | return await self._upload_to_copyparty(file_path) | ||
| 85 | |||
| 86 | @commands.message_command( | ||
| 87 | name="upload", | ||
| 88 | description="wyślij załączniki na copyparty", | ||
| 89 | ) | ||
| 90 | async def message_upload(self, ctx, message: discord.Message): | ||
| 91 | if not message.attachments: | ||
| 92 | await ctx.respond("Brak załączników w wiadomości", ephemeral=True) | ||
| 93 | return | ||
| 94 | |||
| 95 | await ctx.defer() | ||
| 96 | |||
| 97 | uploaded_urls = [] | ||
| 98 | failed_count = 0 | ||
| 99 | |||
| 100 | for attachment in message.attachments: | ||
| 101 | file_path = os.path.join(self.temp_dir, attachment.filename) | ||
| 102 | await attachment.save(file_path) | ||
| 103 | |||
| 104 | url = await self._upload_file(file_path) | ||
| 105 | if url: | ||
| 106 | uploaded_urls.append(url) | ||
| 107 | else: | ||
| 108 | failed_count += 1 | ||
| 109 | |||
| 110 | if os.path.exists(file_path): | ||
| 111 | os.remove(file_path) | ||
| 112 | |||
| 113 | if uploaded_urls: | ||
| 114 | response = f"Wysłano {len(uploaded_urls)} plików:\n" + "\n".join(uploaded_urls) | ||
| 115 | if failed_count > 0: | ||
| 116 | response += f"\n\nNie wysłano {failed_count} plików" | ||
| 117 | await ctx.respond(response) | ||
| 118 | else: | ||
| 119 | await ctx.respond("Nie udało się wysłać żadnych plików", ephemeral=True) | ||
| 120 | |||
| 121 | @commands.slash_command( | ||
| 122 | name="upload", | ||
| 123 | description="wyślij plik na copyparty", | ||
| 124 | ) | ||
| 125 | @discord.option( | ||
| 126 | name="zalacznik", | ||
| 127 | description="plik z discorda", | ||
| 128 | required=True, | ||
| 129 | type=discord.Attachment | ||
| 130 | ) | ||
| 131 | async def upload(self, ctx, zalacznik: discord.Attachment = None): | ||
| 132 | await ctx.defer() | ||
| 133 | |||
| 134 | file_path = None | ||
| 135 | file_path = os.path.join(self.temp_dir, zalacznik.filename) | ||
| 136 | await zalacznik.save(file_path) | ||
| 137 | |||
| 138 | if not file_path or not os.path.exists(file_path): | ||
| 139 | await ctx.respond("Nie udało się pobrać pliku", ephemeral=True) | ||
| 140 | return | ||
| 141 | |||
| 142 | try: | ||
| 143 | dl_url = await self._upload_file(file_path) | ||
| 144 | if dl_url: | ||
| 145 | await ctx.respond(dl_url) | ||
| 146 | else: | ||
| 147 | await ctx.respond("Nie udało się wysłać pliku", ephemeral=True) | ||
| 148 | finally: | ||
| 149 | if file_path and os.path.exists(file_path): | ||
| 150 | os.remove(file_path) | ||
| 151 | |||
| 152 | |||
| 153 | def setup(bot): | ||
| 154 | bot.add_cog(Uploader(bot)) | ||
