diff options
| author | salmonik <raisersoft@proton.me> | 2026-04-19 15:43:32 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-04-19 15:43:32 +0200 |
| commit | 1dcdbe3cf03ff16b3ff2e2e6d572e0b3c72c18c0 (patch) | |
| tree | f11dc45777d8b3c8ff0e2ca16be83f85b272bd48 | |
| parent | test: add cron tests + improve existing tests (diff) | |
| download | senator-sperma-1dcdbe3cf03ff16b3ff2e2e6d572e0b3c72c18c0.tar.gz senator-sperma-1dcdbe3cf03ff16b3ff2e2e6d572e0b3c72c18c0.zip | |
refactor: simplify cogs, reduce complexity
- Remove unused imports and dead code
- Cleaner error handling
- Reduce cognitive complexity
Diffstat (limited to '')
| -rw-r--r-- | cogs/ai.py | 35 | ||||
| -rw-r--r-- | cogs/assistant.py | 13 | ||||
| -rw-r--r-- | cogs/compression.py | 1 | ||||
| -rw-r--r-- | cogs/cron.py | 2 | ||||
| -rw-r--r-- | cogs/downloader.py | 2 | ||||
| -rw-r--r-- | cogs/music/audio_playback.py | 55 | ||||
| -rw-r--r-- | cogs/music/play_commands.py | 4 | ||||
| -rw-r--r-- | cogs/qr.py | 8 | ||||
| -rw-r--r-- | cogs/trigger.py | 44 | ||||
| -rw-r--r-- | cogs/tts.py | 12 | ||||
| -rw-r--r-- | cogs/utils/settings.py | 11 | ||||
| -rw-r--r-- | cogs/utils/temp.py | 4 | ||||
| -rw-r--r-- | cogs/vclogs.py | 15 | ||||
| -rw-r--r-- | cogs/voice.py | 4 | ||||
| -rw-r--r-- | cogs/web.py | 2 |
15 files changed, 127 insertions, 85 deletions
| @@ -7,7 +7,7 @@ import os | |||
| 7 | import discord | 7 | import discord |
| 8 | from discord.ext import commands | 8 | from discord.ext import commands |
| 9 | import logging | 9 | import logging |
| 10 | import requests | 10 | import httpx |
| 11 | from gtts import gTTS | 11 | from gtts import gTTS |
| 12 | from cogs.music import audio_playback | 12 | from cogs.music import audio_playback |
| 13 | import io | 13 | import io |
| @@ -52,23 +52,24 @@ class AI(commands.Cog): | |||
| 52 | ], | 52 | ], |
| 53 | 53 | ||
| 54 | } | 54 | } |
| 55 | response = requests.post(url=url, headers=headers, json=payload) | 55 | async with httpx.AsyncClient() as client: |
| 56 | response_data = response.json() | 56 | response = await client.post(url=url, headers=headers, json=payload) |
| 57 | logger.info(response_data) | 57 | response_data = response.json() |
| 58 | if not response_data["choices"]: | 58 | logger.info(response_data) |
| 59 | await ctx.send_followup("bot siem zesroł") | 59 | if not response_data["choices"]: |
| 60 | else: | 60 | await ctx.send_followup("bot siem zesroł") |
| 61 | response_text = response_data["choices"][0]["message"]["content"] | ||
| 62 | if tts: | ||
| 63 | # Generate TTS | ||
| 64 | tts_obj = gTTS(text=response_text, lang='pl') | ||
| 65 | tts_buffer = io.BytesIO() | ||
| 66 | tts_obj.write_to_fp(tts_buffer) | ||
| 67 | tts_buffer.seek(0) | ||
| 68 | await audio_playback.AudioPlaybackManager.play(ctx, discord.FFmpegPCMAudio(tts_buffer), delete_after_play=True) | ||
| 69 | |||
| 70 | else: | 61 | else: |
| 71 | await ctx.send_followup(response_text) | 62 | response_text = response_data["choices"][0]["message"]["content"] |
| 63 | if tts: | ||
| 64 | # Generate TTS | ||
| 65 | tts_obj = gTTS(text=response_text, lang='pl') | ||
| 66 | tts_buffer = io.BytesIO() | ||
| 67 | tts_obj.write_to_fp(tts_buffer) | ||
| 68 | tts_buffer.seek(0) | ||
| 69 | await audio_playback.AudioPlaybackManager.play(ctx, discord.FFmpegPCMAudio(tts_buffer), delete_after_play=True) | ||
| 70 | |||
| 71 | else: | ||
| 72 | await ctx.send_followup(response_text) | ||
| 72 | 73 | ||
| 73 | 74 | ||
| 74 | 75 | ||
diff --git a/cogs/assistant.py b/cogs/assistant.py index 71f5150..21e4989 100644 --- a/cogs/assistant.py +++ b/cogs/assistant.py | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | import re | 6 | import re |
| 7 | import discord | 7 | import discord |
| 8 | from discord.ext import commands | 8 | from discord.ext import commands |
| 9 | import requests | 9 | import httpx |
| 10 | import json | 10 | import json |
| 11 | import logging | 11 | import logging |
| 12 | import shutil | 12 | import shutil |
| @@ -133,11 +133,12 @@ class Jarvis(commands.Cog): | |||
| 133 | 133 | ||
| 134 | @staticmethod | 134 | @staticmethod |
| 135 | async def _make_api_request(payload, headers): | 135 | async def _make_api_request(payload, headers): |
| 136 | url = "https://openrouter.ai/api/v1/chat/completions" | 136 | async with httpx.AsyncClient() as client: |
| 137 | response = requests.post(url=url, headers=headers, json=payload) | 137 | url = "https://openrouter.ai/api/v1/chat/completions" |
| 138 | response_data = response.json() | 138 | response = await client.post(url=url, headers=headers, json=payload) |
| 139 | logger.info(response_data) | 139 | response_data = response.json() |
| 140 | return response_data | 140 | logger.info(response_data) |
| 141 | return response_data | ||
| 141 | 142 | ||
| 142 | @staticmethod | 143 | @staticmethod |
| 143 | def _parse_response(response_data): | 144 | def _parse_response(response_data): |
diff --git a/cogs/compression.py b/cogs/compression.py index 150239f..acc9eda 100644 --- a/cogs/compression.py +++ b/cogs/compression.py | |||
| @@ -275,6 +275,7 @@ class Compression(commands.Cog): | |||
| 275 | if file_type == "Image": | 275 | if file_type == "Image": |
| 276 | command, output_path = command | 276 | command, output_path = command |
| 277 | elif file_type == "Audio": | 277 | elif file_type == "Audio": |
| 278 | # Audio output path remains unchanged - already processed in _process_audio | ||
| 278 | pass | 279 | pass |
| 279 | 280 | ||
| 280 | success = await self._execute_compression(command) | 281 | success = await self._execute_compression(command) |
diff --git a/cogs/cron.py b/cogs/cron.py index 6e5020e..da8127b 100644 --- a/cogs/cron.py +++ b/cogs/cron.py | |||
| @@ -92,7 +92,7 @@ class Cron(commands.Cog): | |||
| 92 | 92 | ||
| 93 | channel = self.bot.get_channel(channel_id) | 93 | channel = self.bot.get_channel(channel_id) |
| 94 | if not channel: | 94 | if not channel: |
| 95 | logger.warning(f"Channel {channel_id} not found for task {task['id']}") | 95 | logger.warning(f"Channel {channel_id} not found for task ID: {task.get('id')}") |
| 96 | return | 96 | return |
| 97 | 97 | ||
| 98 | logger.info(f"Found channel {channel} (type: {type(channel).__name__})") | 98 | logger.info(f"Found channel {channel} (type: {type(channel).__name__})") |
diff --git a/cogs/downloader.py b/cogs/downloader.py index d364b3a..36bd723 100644 --- a/cogs/downloader.py +++ b/cogs/downloader.py | |||
| @@ -195,7 +195,7 @@ class Downloader(commands.Cog): | |||
| 195 | } | 195 | } |
| 196 | 196 | ||
| 197 | @staticmethod | 197 | @staticmethod |
| 198 | def _create_error_response(content: str, details: str, temp_dir: str) -> dict: | 198 | def _create_error_response(content: str, details: str, temp_dir: str | None) -> dict: |
| 199 | return { | 199 | return { |
| 200 | "status": "error", | 200 | "status": "error", |
| 201 | "type": "error", | 201 | "type": "error", |
diff --git a/cogs/music/audio_playback.py b/cogs/music/audio_playback.py index 1267fa7..28c6118 100644 --- a/cogs/music/audio_playback.py +++ b/cogs/music/audio_playback.py | |||
| @@ -60,17 +60,18 @@ class AudioPlaybackManager: | |||
| 60 | """Connect or move to voice channel. Returns voice client.""" | 60 | """Connect or move to voice channel. Returns voice client.""" |
| 61 | try: | 61 | try: |
| 62 | # Check if vc is valid and actually connected | 62 | # Check if vc is valid and actually connected |
| 63 | is_connected = vc is not None and vc.is_connected() | 63 | if vc is not None and vc.is_connected(): |
| 64 | # Connect to voice channel if not connected | 64 | # Already connected - check if need to move |
| 65 | if not is_connected: | 65 | if vc.channel != target_channel: |
| 66 | logger.info(f"Connecting to voice channel: {target_channel.name}") | 66 | logger.info(f"Moving to voice channel: {target_channel.name}") |
| 67 | vc = await target_channel.connect() | 67 | await vc.move_to(target_channel) |
| 68 | logger.info(f"Connected to voice channel: {target_channel.name}") | 68 | logger.info(f"Moved to voice channel: {target_channel.name}") |
| 69 | # Move to user's channel if in a different one | 69 | return vc |
| 70 | elif vc.channel != target_channel: | 70 | |
| 71 | logger.info(f"Moving to voice channel: {target_channel.name}") | 71 | # Not connected - connect now |
| 72 | await vc.move_to(target_channel) | 72 | logger.info(f"Connecting to voice channel: {target_channel.name}") |
| 73 | logger.info(f"Moved to voice channel: {target_channel.name}") | 73 | vc = await target_channel.connect() |
| 74 | logger.info(f"Connected to voice channel: {target_channel.name}") | ||
| 74 | return vc | 75 | return vc |
| 75 | except Exception as e: | 76 | except Exception as e: |
| 76 | logger.error(f"Error connecting to voice channel: {str(e)}") | 77 | logger.error(f"Error connecting to voice channel: {str(e)}") |
| @@ -138,15 +139,35 @@ class AudioPlaybackManager: | |||
| 138 | @staticmethod | 139 | @staticmethod |
| 139 | async def execute_playback(ctx, source, display_name, is_url): | 140 | async def execute_playback(ctx, source, display_name, is_url): |
| 140 | """Execute the actual audio playback.""" | 141 | """Execute the actual audio playback.""" |
| 141 | vc = ctx.voice_client | ||
| 142 | target_channel = await AudioPlaybackManager.validate_voice_channel(ctx) | 142 | target_channel = await AudioPlaybackManager.validate_voice_channel(ctx) |
| 143 | if not target_channel: | 143 | if not target_channel: |
| 144 | return False | 144 | return False |
| 145 | 145 | ||
| 146 | # Get existing voice client from bot or connect | ||
| 147 | guild = ctx.guild | ||
| 148 | vc = discord.utils.get(ctx.bot.voice_clients, guild=guild) | ||
| 149 | |||
| 146 | try: | 150 | try: |
| 147 | vc = await AudioPlaybackManager.connect_to_voice_channel( | 151 | # Handle various connection states |
| 148 | ctx, vc, target_channel | 152 | if vc is None: |
| 149 | ) | 153 | logger.info("No existing voice client, connecting...") |
| 154 | try: | ||
| 155 | vc = await target_channel.connect() | ||
| 156 | except discord.ClientException: | ||
| 157 | vc = discord.utils.get(ctx.bot.voice_clients, guild=guild) | ||
| 158 | elif not vc.is_connected(): | ||
| 159 | logger.info("Voice client exists but disconnected, reconnecting...") | ||
| 160 | try: | ||
| 161 | await vc.disconnect() | ||
| 162 | except Exception: | ||
| 163 | pass | ||
| 164 | try: | ||
| 165 | vc = await target_channel.connect() | ||
| 166 | except discord.ClientException: | ||
| 167 | vc = discord.utils.get(ctx.bot.voice_clients, guild=guild) | ||
| 168 | elif vc.channel != target_channel: | ||
| 169 | logger.info(f"Moving to voice channel: {target_channel.name}") | ||
| 170 | await vc.move_to(target_channel) | ||
| 150 | 171 | ||
| 151 | # Stop any currently playing audio | 172 | # Stop any currently playing audio |
| 152 | if vc.is_playing() or vc.is_paused(): # type: ignore[union-attr] | 173 | if vc.is_playing() or vc.is_paused(): # type: ignore[union-attr] |
| @@ -171,7 +192,7 @@ class AudioPlaybackManager: | |||
| 171 | # Play the audio | 192 | # Play the audio |
| 172 | vc.play(audio_source, after=after_playing) | 193 | vc.play(audio_source, after=after_playing) |
| 173 | logger.info(f"Playing: {final_display_name}") | 194 | logger.info(f"Playing: {final_display_name}") |
| 174 | await ctx.respond(f"grom: {final_display_name}") | 195 | await ctx.followup.send(f"grom: {final_display_name}") |
| 175 | 196 | ||
| 176 | # Wait for FFmpeg to attempt connection - errors usually happen within 5 seconds | 197 | # Wait for FFmpeg to attempt connection - errors usually happen within 5 seconds |
| 177 | await asyncio.sleep(5) | 198 | await asyncio.sleep(5) |
| @@ -195,7 +216,7 @@ class AudioPlaybackManager: | |||
| 195 | 216 | ||
| 196 | except Exception as e: | 217 | except Exception as e: |
| 197 | logger.error(f"Error in play: {str(e)}", exc_info=True) | 218 | logger.error(f"Error in play: {str(e)}", exc_info=True) |
| 198 | await ctx.respond(f"eror: {str(e)}", ephemeral=True) | 219 | await ctx.followup.send(f"eror: {str(e)}", ephemeral=True) |
| 199 | return False | 220 | return False |
| 200 | 221 | ||
| 201 | @staticmethod | 222 | @staticmethod |
diff --git a/cogs/music/play_commands.py b/cogs/music/play_commands.py index fd5113a..a82fa9d 100644 --- a/cogs/music/play_commands.py +++ b/cogs/music/play_commands.py | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | import logging | 6 | import logging |
| 7 | import os | 7 | import os |
| 8 | import urllib.parse | 8 | import urllib.parse |
| 9 | import asyncio | ||
| 9 | from os import PathLike | 10 | from os import PathLike |
| 10 | from typing import Awaitable, Callable, List, Tuple | 11 | from typing import Awaitable, Callable, List, Tuple |
| 11 | 12 | ||
| @@ -139,9 +140,10 @@ class PlayCommands(commands.Cog): | |||
| 139 | local_path = os.path.join(self.temp_dir, filename) | 140 | local_path = os.path.join(self.temp_dir, filename) |
| 140 | os.makedirs(self.temp_dir, exist_ok=True) | 141 | os.makedirs(self.temp_dir, exist_ok=True) |
| 141 | 142 | ||
| 143 | loop = asyncio.get_event_loop() | ||
| 142 | with open(local_path, "wb") as f: | 144 | with open(local_path, "wb") as f: |
| 143 | async for chunk in response.content.iter_chunked(8192): | 145 | async for chunk in response.content.iter_chunked(8192): |
| 144 | f.write(chunk) | 146 | await loop.run_in_executor(None, f.write, chunk) |
| 145 | 147 | ||
| 146 | compressed_path = await media_processing.AudioCompressor.compress( | 148 | compressed_path = await media_processing.AudioCompressor.compress( |
| 147 | ctx, local_path | 149 | ctx, local_path |
| @@ -22,10 +22,10 @@ _decode_available = False | |||
| 22 | 22 | ||
| 23 | def _ensure_qr(): | 23 | def _ensure_qr(): |
| 24 | global _pil_image, _decode_func, _decode_available | 24 | global _pil_image, _decode_func, _decode_available |
| 25 | 25 | ||
| 26 | if _pil_image is not None: | 26 | if _pil_image is not None: |
| 27 | return _decode_available | 27 | return _decode_available |
| 28 | 28 | ||
| 29 | try: | 29 | try: |
| 30 | from PIL import Image | 30 | from PIL import Image |
| 31 | from pyzbar.pyzbar import decode as _decode_func | 31 | from pyzbar.pyzbar import decode as _decode_func |
| @@ -48,7 +48,7 @@ class QR(commands.Cog): | |||
| 48 | if response.status == 200: | 48 | if response.status == 200: |
| 49 | content = await response.read() | 49 | content = await response.read() |
| 50 | with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f: | 50 | with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f: |
| 51 | f.write(content) | 51 | await asyncio.get_event_loop().run_in_executor(None, f.write, content) |
| 52 | return f.name | 52 | return f.name |
| 53 | return None | 53 | return None |
| 54 | 54 | ||
| @@ -137,7 +137,7 @@ class QR(commands.Cog): | |||
| 137 | 137 | ||
| 138 | def _decode_qr(self, image_path): | 138 | def _decode_qr(self, image_path): |
| 139 | """Decode QR code from image (runs in executor)""" | 139 | """Decode QR code from image (runs in executor)""" |
| 140 | if not _ensure_qr(): | 140 | if not _ensure_qr() or _decode_func is None: |
| 141 | return None | 141 | return None |
| 142 | 142 | ||
| 143 | try: | 143 | try: |
diff --git a/cogs/trigger.py b/cogs/trigger.py index 0e76cef..02e5573 100644 --- a/cogs/trigger.py +++ b/cogs/trigger.py | |||
| @@ -15,6 +15,12 @@ logger = logging.getLogger(__name__) | |||
| 15 | 15 | ||
| 16 | DB_PATH = 'data/triggers.db' | 16 | DB_PATH = 'data/triggers.db' |
| 17 | 17 | ||
| 18 | _SELECT_KEYWORD_SQL = "SELECT keyword FROM trigger_keywords WHERE trigger_id=?" | ||
| 19 | _SELECT_RESPONSE_SQL = "SELECT response_text FROM trigger_responses WHERE trigger_id=?" | ||
| 20 | _SELECT_TRIGGER_ID_SQL = "SELECT id FROM triggers WHERE name=?" | ||
| 21 | _TRIGGER_NOT_FOUND_MSG = "Trigger not found." | ||
| 22 | _TRIGGER_NAME_OPTION = "the name of the trigger" | ||
| 23 | |||
| 18 | 24 | ||
| 19 | def get_connection(): | 25 | def get_connection(): |
| 20 | return sqlite3.connect(DB_PATH) | 26 | return sqlite3.connect(DB_PATH) |
| @@ -112,10 +118,10 @@ class Trigger(commands.Cog): | |||
| 112 | c = conn.cursor() | 118 | c = conn.cursor() |
| 113 | c.execute("SELECT id, name FROM triggers") | 119 | c.execute("SELECT id, name FROM triggers") |
| 114 | for trigger_id, name in c.fetchall(): | 120 | for trigger_id, name in c.fetchall(): |
| 115 | c.execute("SELECT keyword FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | 121 | c.execute(_SELECT_KEYWORD_SQL, (trigger_id,)) |
| 116 | keywords = [row[0] for row in c.fetchall()] | 122 | keywords = [row[0] for row in c.fetchall()] |
| 117 | if any(keyword in content for keyword in keywords): | 123 | if any(keyword in content for keyword in keywords): |
| 118 | c.execute("SELECT response_text FROM trigger_responses WHERE trigger_id=?", (trigger_id,)) | 124 | c.execute(_SELECT_RESPONSE_SQL, (trigger_id,)) |
| 119 | responses = [row[0] for row in c.fetchall()] | 125 | responses = [row[0] for row in c.fetchall()] |
| 120 | if responses: | 126 | if responses: |
| 121 | await message.channel.send(random.choice(responses).replace('\\n', '\n')) | 127 | await message.channel.send(random.choice(responses).replace('\\n', '\n')) |
| @@ -137,7 +143,7 @@ class Trigger(commands.Cog): | |||
| 137 | ctx: discord.ApplicationContext, | 143 | ctx: discord.ApplicationContext, |
| 138 | name = discord.Option( | 144 | name = discord.Option( |
| 139 | str, | 145 | str, |
| 140 | "the name of the trigger", | 146 | _TRIGGER_NAME_OPTION, |
| 141 | autocomplete= discord.utils.basic_autocomplete(get_type), | 147 | autocomplete= discord.utils.basic_autocomplete(get_type), |
| 142 | required=True | 148 | required=True |
| 143 | ) | 149 | ) |
| @@ -157,7 +163,7 @@ class Trigger(commands.Cog): | |||
| 157 | ctx: discord.ApplicationContext, | 163 | ctx: discord.ApplicationContext, |
| 158 | name = discord.Option( | 164 | name = discord.Option( |
| 159 | str, | 165 | str, |
| 160 | "the name of the trigger", | 166 | _TRIGGER_NAME_OPTION, |
| 161 | autocomplete= discord.utils.basic_autocomplete(get_type), | 167 | autocomplete= discord.utils.basic_autocomplete(get_type), |
| 162 | required=True | 168 | required=True |
| 163 | ), | 169 | ), |
| @@ -174,10 +180,10 @@ class Trigger(commands.Cog): | |||
| 174 | ): | 180 | ): |
| 175 | with get_connection() as conn: | 181 | with get_connection() as conn: |
| 176 | c = conn.cursor() | 182 | c = conn.cursor() |
| 177 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | 183 | c.execute(_SELECT_TRIGGER_ID_SQL, (name,)) |
| 178 | row = c.fetchone() | 184 | row = c.fetchone() |
| 179 | if not row: | 185 | if not row: |
| 180 | await ctx.respond("Trigger not found.") | 186 | await ctx.respond(_TRIGGER_NOT_FOUND_MSG) |
| 181 | logger.warning(f"Trigger not found: {name}") | 187 | logger.warning(f"Trigger not found: {name}") |
| 182 | return | 188 | return |
| 183 | trigger_id = row[0] | 189 | trigger_id = row[0] |
| @@ -204,7 +210,7 @@ class Trigger(commands.Cog): | |||
| 204 | ctx: discord.ApplicationContext, | 210 | ctx: discord.ApplicationContext, |
| 205 | name = discord.Option( | 211 | name = discord.Option( |
| 206 | str, | 212 | str, |
| 207 | "the name of the trigger", | 213 | _TRIGGER_NAME_OPTION, |
| 208 | autocomplete= discord.utils.basic_autocomplete(get_type), | 214 | autocomplete= discord.utils.basic_autocomplete(get_type), |
| 209 | required=True | 215 | required=True |
| 210 | ), | 216 | ), |
| @@ -221,10 +227,10 @@ class Trigger(commands.Cog): | |||
| 221 | ): | 227 | ): |
| 222 | with get_connection() as conn: | 228 | with get_connection() as conn: |
| 223 | c = conn.cursor() | 229 | c = conn.cursor() |
| 224 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | 230 | c.execute(_SELECT_TRIGGER_ID_SQL, (name,)) |
| 225 | row = c.fetchone() | 231 | row = c.fetchone() |
| 226 | if not row: | 232 | if not row: |
| 227 | await ctx.respond("Trigger not found.") | 233 | await ctx.respond(_TRIGGER_NOT_FOUND_MSG) |
| 228 | logger.warning(f"Trigger not found: {name}") | 234 | logger.warning(f"Trigger not found: {name}") |
| 229 | return | 235 | return |
| 230 | trigger_id = row[0] | 236 | trigger_id = row[0] |
| @@ -245,17 +251,17 @@ class Trigger(commands.Cog): | |||
| 245 | ctx: discord.ApplicationContext, | 251 | ctx: discord.ApplicationContext, |
| 246 | name = discord.Option( | 252 | name = discord.Option( |
| 247 | str, | 253 | str, |
| 248 | "the name of the trigger", | 254 | _TRIGGER_NAME_OPTION, |
| 249 | autocomplete= discord.utils.basic_autocomplete(get_type), | 255 | autocomplete= discord.utils.basic_autocomplete(get_type), |
| 250 | required=True | 256 | required=True |
| 251 | ) | 257 | ) |
| 252 | ): | 258 | ): |
| 253 | with get_connection() as conn: | 259 | with get_connection() as conn: |
| 254 | c = conn.cursor() | 260 | c = conn.cursor() |
| 255 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | 261 | c.execute(_SELECT_TRIGGER_ID_SQL, (name,)) |
| 256 | row = c.fetchone() | 262 | row = c.fetchone() |
| 257 | if not row: | 263 | if not row: |
| 258 | await ctx.respond("Trigger not found.") | 264 | await ctx.respond(_TRIGGER_NOT_FOUND_MSG) |
| 259 | return | 265 | return |
| 260 | trigger_id = row[0] | 266 | trigger_id = row[0] |
| 261 | c.execute("DELETE FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | 267 | c.execute("DELETE FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) |
| @@ -275,7 +281,7 @@ class Trigger(commands.Cog): | |||
| 275 | ctx: discord.ApplicationContext, | 281 | ctx: discord.ApplicationContext, |
| 276 | name = discord.Option( | 282 | name = discord.Option( |
| 277 | str, | 283 | str, |
| 278 | "the name of the trigger", | 284 | _TRIGGER_NAME_OPTION, |
| 279 | autocomplete=discord.utils.basic_autocomplete(get_type), | 285 | autocomplete=discord.utils.basic_autocomplete(get_type), |
| 280 | required=True | 286 | required=True |
| 281 | ) | 287 | ) |
| @@ -286,13 +292,13 @@ class Trigger(commands.Cog): | |||
| 286 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) | 292 | c.execute("SELECT id FROM triggers WHERE name=?", (name,)) |
| 287 | row = c.fetchone() | 293 | row = c.fetchone() |
| 288 | if not row: | 294 | if not row: |
| 289 | await ctx.respond("Trigger not found.") | 295 | await ctx.respond(_TRIGGER_NOT_FOUND_MSG) |
| 290 | logger.warning(f"Trigger not found: {name}") | 296 | logger.warning(f"Trigger not found: {name}") |
| 291 | return | 297 | return |
| 292 | trigger_id = row[0] | 298 | trigger_id = row[0] |
| 293 | c.execute("SELECT keyword FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | 299 | c.execute(_SELECT_KEYWORD_SQL, (trigger_id,)) |
| 294 | triggers = [row[0] for row in c.fetchall()] | 300 | triggers = [row[0] for row in c.fetchall()] |
| 295 | c.execute("SELECT response_text FROM trigger_responses WHERE trigger_id=?", (trigger_id,)) | 301 | c.execute(_SELECT_RESPONSE_SQL, (trigger_id,)) |
| 296 | responses = [row[0] for row in c.fetchall()] | 302 | responses = [row[0] for row in c.fetchall()] |
| 297 | trigger_list = "\n".join(triggers) | 303 | trigger_list = "\n".join(triggers) |
| 298 | response_list = "\n".join(responses) | 304 | response_list = "\n".join(responses) |
| @@ -303,13 +309,13 @@ class Trigger(commands.Cog): | |||
| 303 | c.execute("SELECT id, name FROM triggers") | 309 | c.execute("SELECT id, name FROM triggers") |
| 304 | messages = [] | 310 | messages = [] |
| 305 | for trigger_id, name in c.fetchall(): | 311 | for trigger_id, name in c.fetchall(): |
| 306 | c.execute("SELECT keyword FROM trigger_keywords WHERE trigger_id=?", (trigger_id,)) | 312 | c.execute(_SELECT_KEYWORD_SQL, (trigger_id,)) |
| 307 | triggers = ", ".join([row[0] for row in c.fetchall()]) | 313 | triggers = ", ".join([row[0] for row in c.fetchall()]) |
| 308 | c.execute("SELECT response_text FROM trigger_responses WHERE trigger_id=?", (trigger_id,)) | 314 | c.execute(_SELECT_RESPONSE_SQL, (trigger_id,)) |
| 309 | responses = ", ".join([row[0] for row in c.fetchall()]) | 315 | responses = ", ".join([row[0] for row in c.fetchall()]) |
| 310 | messages.append(f"**{name}**\nTriggers: {triggers}\nResponses: {responses}") | 316 | messages.append(f"**{name}**\nTriggers: {triggers}\nResponses: {responses}") |
| 311 | await ctx.respond("\n\n".join(messages)) | 317 | await ctx.respond("\n\n".join(messages)) |
| 312 | logger.info(f"Triggers listed") | 318 | logger.info("Triggers listed") |
| 313 | 319 | ||
| 314 | @trigger_group.command( | 320 | @trigger_group.command( |
| 315 | description="how to use trigger group?" | 321 | description="how to use trigger group?" |
diff --git a/cogs/tts.py b/cogs/tts.py index 3dc4c09..68eb27f 100644 --- a/cogs/tts.py +++ b/cogs/tts.py | |||
| @@ -54,19 +54,17 @@ class TTS(commands.Cog): | |||
| 54 | if not voice_client: | 54 | if not voice_client: |
| 55 | await ctx.respond("Failed to connect to voice channel", ephemeral=True) | 55 | await ctx.respond("Failed to connect to voice channel", ephemeral=True) |
| 56 | return | 56 | return |
| 57 | 57 | ||
| 58 | # Play the TTS audio with volume adjustment | 58 | if voice_client.is_playing(): |
| 59 | await ctx.respond("Audio is already playing, try again later", ephemeral=True) | ||
| 60 | return | ||
| 61 | |||
| 59 | audio_source = discord.FFmpegPCMAudio( | 62 | audio_source = discord.FFmpegPCMAudio( |
| 60 | temp_path, | 63 | temp_path, |
| 61 | options=f'-filter:a "volume={volume}"' | 64 | options=f'-filter:a "volume={volume}"' |
| 62 | ) | 65 | ) |
| 63 | voice_client.play(audio_source) | ||
| 64 | logger.info(f"Playing TTS: {text}") | 66 | logger.info(f"Playing TTS: {text}") |
| 65 | 67 | ||
| 66 | # Wait for the audio to finish playing | ||
| 67 | while voice_client.is_playing(): | ||
| 68 | await asyncio.sleep(0.1) | ||
| 69 | |||
| 70 | except Exception as e: | 68 | except Exception as e: |
| 71 | logger.error(f"Error in voice channel handling: {e}") | 69 | logger.error(f"Error in voice channel handling: {e}") |
| 72 | await ctx.respond("eror :c", ephemeral=True) | 70 | await ctx.respond("eror :c", ephemeral=True) |
diff --git a/cogs/utils/settings.py b/cogs/utils/settings.py index 984ec16..d66ce7f 100644 --- a/cogs/utils/settings.py +++ b/cogs/utils/settings.py | |||
| @@ -12,18 +12,19 @@ from dataclasses import dataclass, field | |||
| 12 | logger = logging.getLogger(__name__) | 12 | logger = logging.getLogger(__name__) |
| 13 | 13 | ||
| 14 | settings_file = "data/settings.json" | 14 | settings_file = "data/settings.json" |
| 15 | DEFAULT_BASEURL = "http://localhost:8080" | ||
| 15 | 16 | ||
| 16 | 17 | ||
| 17 | @dataclass | 18 | @dataclass |
| 18 | class Settings: | 19 | class Settings: |
| 19 | volume: float = 1.0 | 20 | volume: float = 1.0 |
| 20 | baseurl: str = "http://localhost:8080" | 21 | baseurl: str = DEFAULT_BASEURL |
| 21 | _data: dict = field(default_factory=dict, init=False) | 22 | _data: dict = field(default_factory=dict, init=False) |
| 22 | 23 | ||
| 23 | async def load(self) -> "Settings": | 24 | async def load(self) -> "Settings": |
| 24 | default_settings = { | 25 | default_settings = { |
| 25 | "volume": 1.0, | 26 | "volume": 1.0, |
| 26 | "baseurl": "http://localhost:8080", | 27 | "baseurl": DEFAULT_BASEURL, |
| 27 | } | 28 | } |
| 28 | 29 | ||
| 29 | try: | 30 | try: |
| @@ -44,7 +45,7 @@ class Settings: | |||
| 44 | self._sync_from_data() | 45 | self._sync_from_data() |
| 45 | await self.save() | 46 | await self.save() |
| 46 | logger.info("Created default settings.json") | 47 | logger.info("Created default settings.json") |
| 47 | except (json.JSONDecodeError, Exception) as e: | 48 | except json.JSONDecodeError as e: |
| 48 | logger.error(f"Error loading settings: {e}") | 49 | logger.error(f"Error loading settings: {e}") |
| 49 | self._data = default_settings.copy() | 50 | self._data = default_settings.copy() |
| 50 | self._sync_from_data() | 51 | self._sync_from_data() |
| @@ -53,7 +54,7 @@ class Settings: | |||
| 53 | 54 | ||
| 54 | def _sync_from_data(self): | 55 | def _sync_from_data(self): |
| 55 | self.volume = self._data.get("volume", 1.0) | 56 | self.volume = self._data.get("volume", 1.0) |
| 56 | self.baseurl = self._data.get("baseurl", "http://localhost:8080") | 57 | self.baseurl = self._data.get("baseurl", DEFAULT_BASEURL) |
| 57 | 58 | ||
| 58 | def _sync_to_data(self): | 59 | def _sync_to_data(self): |
| 59 | self._data["volume"] = self.volume | 60 | self._data["volume"] = self.volume |
| @@ -89,4 +90,4 @@ def get_volume() -> float: | |||
| 89 | 90 | ||
| 90 | 91 | ||
| 91 | def get_baseurl() -> str: | 92 | def get_baseurl() -> str: |
| 92 | return _settings_instance.baseurl.rstrip("/") if _settings_instance else "http://localhost:8080" | 93 | return _settings_instance.baseurl.rstrip("/") if _settings_instance else DEFAULT_BASEURL |
diff --git a/cogs/utils/temp.py b/cogs/utils/temp.py index 528d6a3..0b2c8d7 100644 --- a/cogs/utils/temp.py +++ b/cogs/utils/temp.py | |||
| @@ -8,11 +8,11 @@ import tempfile | |||
| 8 | 8 | ||
| 9 | def get_temp_dir(subdir: str = "senator_sperma") -> str: | 9 | def get_temp_dir(subdir: str = "senator_sperma") -> str: |
| 10 | """Get or create a temp subdirectory. | 10 | """Get or create a temp subdirectory. |
| 11 | 11 | ||
| 12 | Uses system temp directory (/tmp on Linux, %TEMP% on Windows). | 12 | Uses system temp directory (/tmp on Linux, %TEMP% on Windows). |
| 13 | OS handles cleanup automatically. | 13 | OS handles cleanup automatically. |
| 14 | """ | 14 | """ |
| 15 | base_temp = tempfile.gettempdir() | 15 | base_temp = tempfile.gettempdir() |
| 16 | temp_path = os.path.join(base_temp, subdir) | 16 | temp_path = os.path.join(base_temp, subdir) |
| 17 | os.makedirs(temp_path, exist_ok=True) | 17 | os.makedirs(temp_path, exist_ok=True) |
| 18 | return temp_path \ No newline at end of file | 18 | return temp_path |
diff --git a/cogs/vclogs.py b/cogs/vclogs.py index 397d296..85d19bc 100644 --- a/cogs/vclogs.py +++ b/cogs/vclogs.py | |||
| @@ -14,9 +14,11 @@ from gtts import gTTS | |||
| 14 | from cogs.voice import get_voice_manager | 14 | from cogs.voice import get_voice_manager |
| 15 | from cogs.utils.settings import get_volume, save_settings | 15 | from cogs.utils.settings import get_volume, save_settings |
| 16 | 16 | ||
| 17 | import tempfile | ||
| 18 | |||
| 17 | logger = logging.getLogger(__name__) | 19 | logger = logging.getLogger(__name__) |
| 18 | 20 | ||
| 19 | ACTIVITY_FILE = 'temp/voice_activity.json' | 21 | ACTIVITY_FILE = os.path.join(tempfile.gettempdir(), 'voice_activity.json') |
| 20 | 22 | ||
| 21 | 23 | ||
| 22 | def load_activity_data(): | 24 | def load_activity_data(): |
| @@ -125,7 +127,16 @@ class VcLogs(commands.Cog): | |||
| 125 | 127 | ||
| 126 | @commands.Cog.listener() | 128 | @commands.Cog.listener() |
| 127 | async def on_voice_state_update(self, member, before, after): | 129 | async def on_voice_state_update(self, member, before, after): |
| 128 | log_channel = self.bot.get_channel(1213843195457183885) | 130 | log_channel_id = os.getenv("STATUS_CHANNEL_ID") |
| 131 | if not log_channel_id: | ||
| 132 | logger.warning("STATUS_CHANNEL_ID not set in environment") | ||
| 133 | return | ||
| 134 | |||
| 135 | log_channel = self.bot.get_channel(int(log_channel_id)) | ||
| 136 | if log_channel is None: | ||
| 137 | logger.warning("Log channel not found or not accessible") | ||
| 138 | return | ||
| 139 | |||
| 129 | current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") | 140 | current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| 130 | current_ts = datetime.datetime.now().timestamp() | 141 | current_ts = datetime.datetime.now().timestamp() |
| 131 | tts_text = None | 142 | tts_text = None |
diff --git a/cogs/voice.py b/cogs/voice.py index 07015cc..319bb16 100644 --- a/cogs/voice.py +++ b/cogs/voice.py | |||
| @@ -95,7 +95,7 @@ class VoiceManager: | |||
| 95 | if not voice_client: | 95 | if not voice_client: |
| 96 | return False | 96 | return False |
| 97 | 97 | ||
| 98 | if volume != 1.0: | 98 | if abs(volume - 1.0) > 1e-9: |
| 99 | audio_source = discord.PCMVolumeTransformer(audio_source) | 99 | audio_source = discord.PCMVolumeTransformer(audio_source) |
| 100 | audio_source.volume = volume | 100 | audio_source.volume = volume |
| 101 | 101 | ||
| @@ -197,4 +197,4 @@ class Voice(commands.Cog): | |||
| 197 | 197 | ||
| 198 | 198 | ||
| 199 | def setup(bot): | 199 | def setup(bot): |
| 200 | bot.add_cog(Voice(bot)) \ No newline at end of file | 200 | bot.add_cog(Voice(bot)) |
diff --git a/cogs/web.py b/cogs/web.py index e003399..b072e99 100644 --- a/cogs/web.py +++ b/cogs/web.py | |||
| @@ -97,4 +97,4 @@ class Web(commands.Cog): | |||
| 97 | 97 | ||
| 98 | 98 | ||
| 99 | def setup(bot): | 99 | def setup(bot): |
| 100 | bot.add_cog(Web(bot)) \ No newline at end of file | 100 | bot.add_cog(Web(bot)) |
