diff options
Diffstat (limited to '')
| -rw-r--r-- | cogs/vclogs.py | 316 |
1 files changed, 0 insertions, 316 deletions
diff --git a/cogs/vclogs.py b/cogs/vclogs.py deleted file mode 100644 index 35826d4..0000000 --- a/cogs/vclogs.py +++ /dev/null | |||
| @@ -1,316 +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 | |||
| 6 | import datetime | ||
| 7 | import logging | ||
| 8 | import os | ||
| 9 | import asyncio | ||
| 10 | import discord | ||
| 11 | from discord.ext import commands | ||
| 12 | from gtts import gTTS | ||
| 13 | from cogs.voice import get_voice_manager | ||
| 14 | from cogs.utils.settings import get_volume, save_settings | ||
| 15 | from cogs.utils.database import get_connection, init_database | ||
| 16 | |||
| 17 | import tempfile | ||
| 18 | |||
| 19 | logger = logging.getLogger(__name__) | ||
| 20 | |||
| 21 | init_database() | ||
| 22 | |||
| 23 | |||
| 24 | def load_activity_data(): | ||
| 25 | try: | ||
| 26 | with get_connection() as conn: | ||
| 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") | ||
| 29 | rows = c.fetchall() | ||
| 30 | |||
| 31 | return { | ||
| 32 | row[0]: { | ||
| 33 | "join_time": row[1], | ||
| 34 | "total_active": row[2], | ||
| 35 | "total_afk": row[3], | ||
| 36 | "is_afk": bool(row[4]), | ||
| 37 | "afk_start": row[5], | ||
| 38 | "active_start": row[6], | ||
| 39 | "channel_name": row[7], | ||
| 40 | } | ||
| 41 | for row in rows | ||
| 42 | } | ||
| 43 | except Exception as e: | ||
| 44 | logger.error(f"Error loading voice activity: {e}") | ||
| 45 | return {} | ||
| 46 | |||
| 47 | |||
| 48 | def save_activity_data(data): | ||
| 49 | try: | ||
| 50 | with get_connection() as conn: | ||
| 51 | c = conn.cursor() | ||
| 52 | c.execute("PRAGMA foreign_keys = ON") | ||
| 53 | |||
| 54 | c.execute("DELETE FROM voice_activity") | ||
| 55 | |||
| 56 | for user_id, user_data in data.items(): | ||
| 57 | c.execute( | ||
| 58 | """INSERT INTO voice_activity (user_id, join_time, total_active, total_afk, is_afk, afk_start, active_start, channel_name) | ||
| 59 | VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", | ||
| 60 | ( | ||
| 61 | user_id, | ||
| 62 | user_data.get("join_time"), | ||
| 63 | user_data.get("total_active", 0), | ||
| 64 | user_data.get("total_afk", 0), | ||
| 65 | int(user_data.get("is_afk", False)), | ||
| 66 | user_data.get("afk_start"), | ||
| 67 | user_data.get("active_start"), | ||
| 68 | user_data.get("channel_name"), | ||
| 69 | ), | ||
| 70 | ) | ||
| 71 | conn.commit() | ||
| 72 | logger.info(f"Saved voice activity for {len(data)} users") | ||
| 73 | except Exception as e: | ||
| 74 | logger.error(f"Error saving voice activity: {e}") | ||
| 75 | |||
| 76 | |||
| 77 | def format_duration(seconds): | ||
| 78 | hours, remainder = divmod(int(seconds), 3600) | ||
| 79 | minutes, seconds = divmod(remainder, 60) | ||
| 80 | if hours > 0: | ||
| 81 | return f"{hours}h {minutes}m {seconds}s" | ||
| 82 | elif minutes > 0: | ||
| 83 | return f"{minutes}m {seconds}s" | ||
| 84 | return f"{seconds}s" | ||
| 85 | |||
| 86 | |||
| 87 | class VcLogs(commands.Cog): | ||
| 88 | def __init__(self, bot): | ||
| 89 | self.bot = bot | ||
| 90 | self._voice_manager = None | ||
| 91 | |||
| 92 | def get_voice_manager(self): | ||
| 93 | if self._voice_manager is None: | ||
| 94 | self._voice_manager = get_voice_manager(self.bot) | ||
| 95 | return self._voice_manager | ||
| 96 | |||
| 97 | @staticmethod | ||
| 98 | async def handle_state_change(member, channel, state_type, is_enabled, log_channel): | ||
| 99 | """Helper method to handle voice state changes""" | ||
| 100 | state_messages = { | ||
| 101 | 'deaf': { | ||
| 102 | True: f"{member.display_name} jest bity przez ojca", | ||
| 103 | False: f"{member.display_name} uciek od ojca" | ||
| 104 | }, | ||
| 105 | 'mute': { | ||
| 106 | True: f"{member.display_name} idzie robić jajecznice", | ||
| 107 | False: f"{member.display_name} zrobił jajecznice" | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | tts_text = state_messages[state_type][is_enabled] | ||
| 112 | return tts_text, channel | ||
| 113 | |||
| 114 | async def play_tts(self, channel, text): | ||
| 115 | from cogs.utils.temp import get_temp_dir | ||
| 116 | |||
| 117 | voice_mgr = self.get_voice_manager() | ||
| 118 | temp_dir = get_temp_dir() | ||
| 119 | tts_file = os.path.join(temp_dir, f"temp_tts_{os.getpid()}_{asyncio.get_event_loop().time()}.mp3") | ||
| 120 | tts = gTTS(text=text, lang='pl') | ||
| 121 | logger.info("saving file") | ||
| 122 | |||
| 123 | loop = asyncio.get_event_loop() | ||
| 124 | await loop.run_in_executor(None, tts.save, tts_file) | ||
| 125 | |||
| 126 | try: | ||
| 127 | voice_status = await voice_mgr.get_voice_status(channel.guild) | ||
| 128 | voice_client = voice_status[0] if voice_status else None | ||
| 129 | is_connected = voice_status[1] if voice_status else False | ||
| 130 | is_playing = voice_status[2] if voice_status else False | ||
| 131 | |||
| 132 | if is_connected: | ||
| 133 | from cogs.voice import is_voice_client_alive | ||
| 134 | if not is_voice_client_alive(voice_client, channel.guild): | ||
| 135 | logger.warning("Stale voice client in play_tts, reconnecting") | ||
| 136 | try: | ||
| 137 | await voice_client.disconnect() | ||
| 138 | except Exception: | ||
| 139 | pass | ||
| 140 | is_connected = False | ||
| 141 | |||
| 142 | if not is_connected: | ||
| 143 | voice_client = await voice_mgr.join_channel(channel) | ||
| 144 | voice_status = await voice_mgr.get_voice_status(channel.guild) | ||
| 145 | voice_client = voice_status[0] if voice_status else None | ||
| 146 | is_connected = voice_status[1] if voice_status else False | ||
| 147 | is_playing = voice_status[2] if voice_status else False | ||
| 148 | |||
| 149 | if not is_connected or voice_client is None: | ||
| 150 | logger.error("Failed to join voice channel") | ||
| 151 | return | ||
| 152 | |||
| 153 | volume = get_volume() | ||
| 154 | logger.info(f"connected: {voice_client}") | ||
| 155 | |||
| 156 | if voice_client: | ||
| 157 | if is_playing: | ||
| 158 | logger.info("voice already playing, skipping TTS") | ||
| 159 | return | ||
| 160 | |||
| 161 | logger.info("preparing to play audio") | ||
| 162 | audio_source = discord.FFmpegPCMAudio(tts_file) | ||
| 163 | await voice_mgr.play_source(channel, audio_source, volume=volume) | ||
| 164 | except Exception as e: | ||
| 165 | logger.error(f"TTS playback error: {e}") | ||
| 166 | finally: | ||
| 167 | if os.path.exists(tts_file): | ||
| 168 | os.remove(tts_file) | ||
| 169 | |||
| 170 | @commands.slash_command( | ||
| 171 | name="log_volume" | ||
| 172 | ) | ||
| 173 | async def volume(self, ctx: discord.ApplicationContext, volume: int): | ||
| 174 | from cogs.utils.settings import get_settings | ||
| 175 | settings = get_settings() | ||
| 176 | settings.volume = volume | ||
| 177 | await save_settings() | ||
| 178 | await ctx.followup.send(f"Volume has been set to: {volume}") | ||
| 179 | logger.info(f"Volume changed to: {volume}") | ||
| 180 | |||
| 181 | @commands.Cog.listener() | ||
| 182 | async def on_voice_state_update(self, member, before, after): | ||
| 183 | log_channel_id = os.getenv("STATUS_CHANNEL_ID") | ||
| 184 | if not log_channel_id: | ||
| 185 | logger.warning("STATUS_CHANNEL_ID not set in environment") | ||
| 186 | return | ||
| 187 | |||
| 188 | log_channel = self.bot.get_channel(int(log_channel_id)) | ||
| 189 | if log_channel is None: | ||
| 190 | logger.warning("Log channel not found or not accessible") | ||
| 191 | return | ||
| 192 | |||
| 193 | current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") | ||
| 194 | current_ts = datetime.datetime.now().timestamp() | ||
| 195 | tts_text = None | ||
| 196 | tts_channel = None | ||
| 197 | |||
| 198 | if member.bot: | ||
| 199 | return | ||
| 200 | |||
| 201 | user_id = str(member.id) | ||
| 202 | activity_data = load_activity_data() | ||
| 203 | |||
| 204 | if before.channel is None and after.channel is not None: | ||
| 205 | is_afk = after.self_mute or after.self_deaf | ||
| 206 | activity_data[user_id] = { | ||
| 207 | 'join_time': current_ts, | ||
| 208 | 'total_active': 0, | ||
| 209 | 'total_afk': 0, | ||
| 210 | 'is_afk': is_afk, | ||
| 211 | 'afk_start': current_ts if is_afk else None, | ||
| 212 | 'active_start': current_ts if not is_afk else None, | ||
| 213 | 'channel_name': after.channel.name | ||
| 214 | } | ||
| 215 | save_activity_data(activity_data) | ||
| 216 | |||
| 217 | message = f"[{current_time}] {member.name} dołączył na {after.channel.name} <t:{int(current_ts)}:R>" | ||
| 218 | tts_text = f"{member.display_name} sie zespawnił" | ||
| 219 | tts_channel = after.channel | ||
| 220 | await log_channel.send(message) | ||
| 221 | logger.info(f"User joined voice channel: {message}") | ||
| 222 | |||
| 223 | elif before.channel is not None and after.channel is None: | ||
| 224 | if user_id in activity_data: | ||
| 225 | data = activity_data[user_id] | ||
| 226 | |||
| 227 | if data.get('is_afk') and data.get('afk_start'): | ||
| 228 | afk_duration = current_ts - data['afk_start'] | ||
| 229 | data['total_afk'] += afk_duration | ||
| 230 | elif data.get('active_start'): | ||
| 231 | active_since_start = current_ts - data['active_start'] | ||
| 232 | data['total_active'] += active_since_start | ||
| 233 | |||
| 234 | active_str = format_duration(data['total_active']) | ||
| 235 | afk_str = format_duration(data['total_afk']) | ||
| 236 | |||
| 237 | message = ( | ||
| 238 | f"[{current_time}] {member.name} rozłączył się z {before.channel.name} <t:{int(current_ts)}:R>\n" | ||
| 239 | f"Aktywny: {active_str} | AFK: {afk_str}" | ||
| 240 | ) | ||
| 241 | tts_text = f"{member.display_name} jest chujem" | ||
| 242 | tts_channel = before.channel | ||
| 243 | await log_channel.send(message) | ||
| 244 | logger.info(f"User left voice channel: {message}") | ||
| 245 | |||
| 246 | del activity_data[user_id] | ||
| 247 | save_activity_data(activity_data) | ||
| 248 | else: | ||
| 249 | message = f"[{current_time}] {member.name} rozłączył się z {before.channel.name} <t:{int(current_ts)}:R>" | ||
| 250 | tts_text = f"{member.display_name} jest chujem" | ||
| 251 | tts_channel = before.channel | ||
| 252 | await log_channel.send(message) | ||
| 253 | logger.info(f"User left voice channel: {message}") | ||
| 254 | |||
| 255 | elif user_id in activity_data: | ||
| 256 | data = activity_data[user_id] | ||
| 257 | |||
| 258 | if data.get('is_afk') and not (after.self_mute or after.self_deaf): | ||
| 259 | afk_duration = current_ts - data['afk_start'] | ||
| 260 | data['total_afk'] += afk_duration | ||
| 261 | data['afk_start'] = None | ||
| 262 | data['is_afk'] = False | ||
| 263 | data['active_start'] = current_ts | ||
| 264 | save_activity_data(activity_data) | ||
| 265 | |||
| 266 | afk_str = format_duration(afk_duration) | ||
| 267 | |||
| 268 | if after.self_mute: | ||
| 269 | activity = "odciszył" | ||
| 270 | else: | ||
| 271 | activity = "odgłuchnął" | ||
| 272 | |||
| 273 | message = ( f"[{current_time}] {member.name} {activity} się na {after.channel.name} <t:{int(current_ts)}:R>\n" | ||
| 274 | f"AFK: {afk_str}" ) | ||
| 275 | await log_channel.send(message) | ||
| 276 | logger.info(f"User returned from AFK: {message}") | ||
| 277 | elif not data.get('is_afk') and (after.self_mute or after.self_deaf): | ||
| 278 | active_since_start = 0 | ||
| 279 | if data.get('active_start'): | ||
| 280 | active_since_start = current_ts - data['active_start'] | ||
| 281 | data['total_active'] += active_since_start | ||
| 282 | data['afk_start'] = current_ts | ||
| 283 | data['is_afk'] = True | ||
| 284 | data['active_start'] = None | ||
| 285 | save_activity_data(activity_data) | ||
| 286 | |||
| 287 | if after.self_mute: | ||
| 288 | activity = "wyciszył" | ||
| 289 | else: | ||
| 290 | activity = "ogłuchnął" | ||
| 291 | |||
| 292 | active_str = format_duration(active_since_start) | ||
| 293 | message = ( f"[{current_time}] {member.name} {activity} się na {after.channel.name} <t:{int(current_ts)}:R>\n" | ||
| 294 | f"Aktywny: {active_str}" ) | ||
| 295 | await log_channel.send(message) | ||
| 296 | logger.info(f"User went AFK: {message}") | ||
| 297 | |||
| 298 | if before.self_deaf != after.self_deaf: | ||
| 299 | tts_text, tts_channel = await self.handle_state_change( | ||
| 300 | member, after.channel, 'deaf', after.self_deaf, log_channel | ||
| 301 | ) | ||
| 302 | if tts_text and tts_channel: | ||
| 303 | await self.play_tts(tts_channel, tts_text) | ||
| 304 | return | ||
| 305 | |||
| 306 | if before.self_mute != after.self_mute: | ||
| 307 | tts_text, tts_channel = await self.handle_state_change( | ||
| 308 | member, after.channel, 'mute', after.self_mute, log_channel | ||
| 309 | ) | ||
| 310 | |||
| 311 | if tts_text and tts_channel: | ||
| 312 | await self.play_tts(tts_channel, tts_text) | ||
| 313 | |||
| 314 | |||
| 315 | def setup(bot): | ||
| 316 | bot.add_cog(VcLogs(bot)) | ||
