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