aboutsummaryrefslogtreecommitdiffstats
path: root/cogs
diff options
context:
space:
mode:
Diffstat (limited to 'cogs')
-rw-r--r--cogs/cron.py11
-rw-r--r--cogs/downloader.py14
-rw-r--r--cogs/music/media_processing.py2
-rw-r--r--cogs/music/play_commands.py2
-rw-r--r--cogs/utils/database.py17
-rw-r--r--cogs/utils/url_helper.py21
-rw-r--r--cogs/vclogs.py55
-rw-r--r--cogs/web.py2
8 files changed, 84 insertions, 40 deletions
diff --git a/cogs/cron.py b/cogs/cron.py
index b4f494a..aad43d2 100644
--- a/cogs/cron.py
+++ b/cogs/cron.py
@@ -30,13 +30,10 @@ class Cron(commands.Cog):
30 30
31 def load_tasks(self): 31 def load_tasks(self):
32 try: 32 try:
33 conn = get_connection() 33 with get_connection() as conn:
34 try:
35 c = conn.cursor() 34 c = conn.cursor()
36 c.execute("SELECT id, cron_expression, type, channel_id, active, audio_filename, message FROM cron_jobs") 35 c.execute("SELECT id, cron_expression, type, channel_id, active, audio_filename, message FROM cron_jobs")
37 rows = c.fetchall() 36 rows = c.fetchall()
38 finally:
39 conn.close()
40 37
41 self.scheduled_tasks = [ 38 self.scheduled_tasks = [
42 { 39 {
@@ -57,8 +54,7 @@ class Cron(commands.Cog):
57 54
58 def save_tasks(self): 55 def save_tasks(self):
59 try: 56 try:
60 conn = get_connection() 57 with get_connection() as conn:
61 try:
62 c = conn.cursor() 58 c = conn.cursor()
63 c.execute("PRAGMA foreign_keys = ON") 59 c.execute("PRAGMA foreign_keys = ON")
64 60
@@ -78,10 +74,7 @@ class Cron(commands.Cog):
78 task.get("message"), 74 task.get("message"),
79 ), 75 ),
80 ) 76 )
81
82 conn.commit() 77 conn.commit()
83 finally:
84 conn.close()
85 except Exception as e: 78 except Exception as e:
86 logger.error(f"Error saving cron jobs: {e}") 79 logger.error(f"Error saving cron jobs: {e}")
87 80
diff --git a/cogs/downloader.py b/cogs/downloader.py
index d829ed7..900bafb 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
14import aiofiles 14import aiofiles
15from cogs.utils.url_helper import clean_url_for_ytdlp
15 16
16logger = logging.getLogger(__name__) 17logger = logging.getLogger(__name__)
17 18
@@ -61,6 +62,10 @@ class Downloader(commands.Cog):
61 file_data = response["content"] 62 file_data = response["content"]
62 await ctx.respond(file=discord.File(file_data)) 63 await ctx.respond(file=discord.File(file_data))
63 else: 64 else:
65 error_msg = response["content"]
66 if 'details' in response:
67 error_msg += f"\n{response['details']}"
68 await ctx.respond(f"Błąd: {error_msg}", ephemeral=True)
64 logger.error(f"Error: {response['content']}") 69 logger.error(f"Error: {response['content']}")
65 if 'details' in response: 70 if 'details' in response:
66 logger.error(f"Details: {response['details']}") 71 logger.error(f"Details: {response['details']}")
@@ -68,6 +73,7 @@ class Downloader(commands.Cog):
68 @staticmethod 73 @staticmethod
69 async def download_standalone(url: str, catbox: bool = False, fformat: str = "mp4", filename_user: str | None = None): 74 async def download_standalone(url: str, catbox: bool = False, fformat: str = "mp4", filename_user: str | None = None):
70 try: 75 try:
76 url = clean_url_for_ytdlp(url)
71 logger.info(f"Downloading {url} as {fformat}") 77 logger.info(f"Downloading {url} as {fformat}")
72 78
73 with tempfile.TemporaryDirectory(delete=False) as temp_dir: 79 with tempfile.TemporaryDirectory(delete=False) as temp_dir:
@@ -157,12 +163,18 @@ class Downloader(commands.Cog):
157 files = {'f': (filename, content, 'application/octet-stream')} 163 files = {'f': (filename, content, 'application/octet-stream')}
158 headers = {'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}'} 164 headers = {'Cookie': f'cppwd={os.getenv("COPYPARTY_PASSWORD")}'}
159 params = {'j': '1', 'ck': 'b2'} 165 params = {'j': '1', 'ck': 'b2'}
160 base_url = "https://catbox.2137697.xyz/sperma/" 166 base_url = os.getenv("COPYPARTY_URL_UPLOAD")
161 167
162 response = requests.post(base_url, files=files, headers=headers, params=params) 168 response = requests.post(base_url, files=files, headers=headers, params=params)
163 169
164 if response.status_code in [200, 201]: 170 if response.status_code in [200, 201]:
165 return Downloader._handle_upload_success(response, filename, temp_dir) 171 return Downloader._handle_upload_success(response, filename, temp_dir)
172 elif response.status_code == 413:
173 return Downloader._create_error_response(
174 "File too large for upload",
175 "Plik jest za duży, by wysłać go na serwer (limit to ok. 100MB)",
176 temp_dir
177 )
166 else: 178 else:
167 return Downloader._create_error_response( 179 return Downloader._create_error_response(
168 f"Server returned {response.status_code}", 180 f"Server returned {response.status_code}",
diff --git a/cogs/music/media_processing.py b/cogs/music/media_processing.py
index b9b814c..91bd406 100644
--- a/cogs/music/media_processing.py
+++ b/cogs/music/media_processing.py
@@ -9,6 +9,7 @@ import os
9 9
10import yt_dlp 10import yt_dlp
11from youtubesearchpython import VideosSearch 11from youtubesearchpython import VideosSearch
12from cogs.utils.url_helper import clean_url_for_ytdlp
12 13
13logger = logging.getLogger(__name__) 14logger = logging.getLogger(__name__)
14 15
@@ -36,6 +37,7 @@ class MediaDownloader:
36 } 37 }
37 38
38 try: 39 try:
40 url = clean_url_for_ytdlp(url)
39 with yt_dlp.YoutubeDL(ytdl_opts) as ydl: 41 with yt_dlp.YoutubeDL(ytdl_opts) as ydl:
40 info_dict = ydl.extract_info(url, download=True) 42 info_dict = ydl.extract_info(url, download=True)
41 # Get the base filename without extension 43 # Get the base filename without extension
diff --git a/cogs/music/play_commands.py b/cogs/music/play_commands.py
index c53dbc7..bdb021d 100644
--- a/cogs/music/play_commands.py
+++ b/cogs/music/play_commands.py
@@ -19,6 +19,7 @@ from discord.ext import commands
19from cogs.music import audio_playback, media_processing 19from cogs.music import audio_playback, media_processing
20from cogs.utils.file_search import UniversalSearcher 20from cogs.utils.file_search import UniversalSearcher
21from cogs.utils.settings import get_uploadurl 21from cogs.utils.settings import get_uploadurl
22from cogs.utils.url_helper import clean_url_for_ytdlp
22 23
23logger = logging.getLogger(__name__) 24logger = logging.getLogger(__name__)
24 25
@@ -193,6 +194,7 @@ class PlayCommands(commands.Cog):
193 } 194 }
194 195
195 loop = asyncio.get_event_loop() 196 loop = asyncio.get_event_loop()
197 url = clean_url_for_ytdlp(url)
196 with YoutubeDL(ytdl_opts) as ydl: 198 with YoutubeDL(ytdl_opts) as ydl:
197 info_dict = await loop.run_in_executor( 199 info_dict = await loop.run_in_executor(
198 None, lambda: ydl.extract_info(url, download=True) 200 None, lambda: ydl.extract_info(url, download=True)
diff --git a/cogs/utils/database.py b/cogs/utils/database.py
index 67cbc9f..ea7d356 100644
--- a/cogs/utils/database.py
+++ b/cogs/utils/database.py
@@ -1,6 +1,7 @@
1import os 1import os
2import sqlite3 2import sqlite3
3import logging 3import logging
4from contextlib import contextmanager
4 5
5logger = logging.getLogger(__name__) 6logger = logging.getLogger(__name__)
6 7
@@ -10,7 +11,8 @@ _initialized = False
10 11
11 12
12def get_connection(): 13def get_connection():
13 return sqlite3.connect(get_db_path()) 14 conn = sqlite3.connect(get_db_path())
15 return _ConnectionContextManager(conn)
14 16
15 17
16def get_db_path(): 18def get_db_path():
@@ -22,6 +24,19 @@ def set_db_path(path):
22 DB_PATH = path 24 DB_PATH = path
23 25
24 26
27class _ConnectionContextManager:
28 """Context manager for sqlite3 connections that auto-closes."""
29 def __init__(self, conn):
30 self.conn = conn
31
32 def __enter__(self):
33 return self.conn
34
35 def __exit__(self, exc_type, exc_val, exc_tb):
36 self.conn.close()
37 return False
38
39
25def init_database(): 40def init_database():
26 global _initialized 41 global _initialized
27 if _initialized: 42 if _initialized:
diff --git a/cogs/utils/url_helper.py b/cogs/utils/url_helper.py
new file mode 100644
index 0000000..eac06f1
--- /dev/null
+++ b/cogs/utils/url_helper.py
@@ -0,0 +1,21 @@
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
7def clean_url_for_ytdlp(url: str) -> str:
8 """Clean URL by removing everything after the first '&'.
9
10 This prevents yt-dlp from interpreting additional query parameters
11 (like &list=...) as playlist URLs.
12
13 Example:
14 Input: https://www.youtube.com/watch?v=lPGipwoJiOM&list=RDlPGipwoJiOM&
15 Output: https://www.youtube.com/watch?v=lPGipwoJiOM
16 """
17 # Find the first '&' and truncate
18 first_ampersand = url.find('&')
19 if first_ampersand != -1:
20 return url[:first_ampersand]
21 return url
diff --git a/cogs/vclogs.py b/cogs/vclogs.py
index 404ffe0..e5ca202 100644
--- a/cogs/vclogs.py
+++ b/cogs/vclogs.py
@@ -23,11 +23,10 @@ init_database()
23 23
24def load_activity_data(): 24def load_activity_data():
25 try: 25 try:
26 conn = get_connection() 26 with get_connection() as conn:
27 c = conn.cursor() 27 c = conn.cursor()
28 c.execute("SELECT user_id, join_time, total_active, total_afk, is_afk, afk_start, active_start, channel_name FROM voice_activity") 28 c.execute("SELECT user_id, join_time, total_active, total_afk, is_afk, afk_start, active_start, channel_name FROM voice_activity")
29 rows = c.fetchall() 29 rows = c.fetchall()
30 conn.close()
31 30
32 return { 31 return {
33 row[0]: { 32 row[0]: {
@@ -48,30 +47,28 @@ def load_activity_data():
48 47
49def save_activity_data(data): 48def save_activity_data(data):
50 try: 49 try:
51 conn = get_connection() 50 with get_connection() as conn:
52 c = conn.cursor() 51 c = conn.cursor()
53 c.execute("PRAGMA foreign_keys = ON") 52 c.execute("PRAGMA foreign_keys = ON")
54 53
55 c.execute("DELETE FROM voice_activity") 54 c.execute("DELETE FROM voice_activity")
56 55
57 for user_id, user_data in data.items(): 56 for user_id, user_data in data.items():
58 c.execute( 57 c.execute(
59 """INSERT INTO voice_activity (user_id, join_time, total_active, total_afk, is_afk, afk_start, active_start, channel_name) 58 """INSERT INTO voice_activity (user_id, join_time, total_active, total_afk, is_afk, afk_start, active_start, channel_name)
60 VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", 59 VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
61 ( 60 (
62 user_id, 61 user_id,
63 user_data.get("join_time"), 62 user_data.get("join_time"),
64 user_data.get("total_active", 0), 63 user_data.get("total_active", 0),
65 user_data.get("total_afk", 0), 64 user_data.get("total_afk", 0),
66 int(user_data.get("is_afk", False)), 65 int(user_data.get("is_afk", False)),
67 user_data.get("afk_start"), 66 user_data.get("afk_start"),
68 user_data.get("active_start"), 67 user_data.get("active_start"),
69 user_data.get("channel_name"), 68 user_data.get("channel_name"),
70 ), 69 ),
71 ) 70 )
72 71 conn.commit()
73 conn.commit()
74 conn.close()
75 logger.info(f"Saved voice activity for {len(data)} users") 72 logger.info(f"Saved voice activity for {len(data)} users")
76 except Exception as e: 73 except Exception as e:
77 logger.error(f"Error saving voice activity: {e}") 74 logger.error(f"Error saving voice activity: {e}")
diff --git a/cogs/web.py b/cogs/web.py
index eea13b1..fd22f9f 100644
--- a/cogs/web.py
+++ b/cogs/web.py
@@ -11,6 +11,7 @@ from bs4 import BeautifulSoup
11import urllib.parse 11import urllib.parse
12import yt_dlp 12import yt_dlp
13import logging 13import logging
14from cogs.utils.url_helper import clean_url_for_ytdlp
14 15
15logger = logging.getLogger(__name__) 16logger = logging.getLogger(__name__)
16 17
@@ -24,6 +25,7 @@ class Web(commands.Cog):
24 if "youtube.com/watch" not in url: 25 if "youtube.com/watch" not in url:
25 return None 26 return None
26 27
28 url = clean_url_for_ytdlp(url)
27 logger.info("youtube link") 29 logger.info("youtube link")
28 try: 30 try:
29 ydl_opts = {"quiet": True, "nowarnings": True} 31 ydl_opts = {"quiet": True, "nowarnings": True}