aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/vclogs.py
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-07-08 02:13:35 +0200
committersalmonik <raisersoft@proton.me>2026-07-08 02:13:35 +0200
commitcd8ed8bc5793985df2c1692072a932ceee517b95 (patch)
tree42d18a4140259b5c68b4dd05b7fcb398f78b8d7f /cogs/vclogs.py
parentMerge pull request 'Voice related fixes, alongside simplifying assistant' (#5... (diff)
downloadsenator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.tar.gz
senator-sperma-cd8ed8bc5793985df2c1692072a932ceee517b95.zip
OPEN THE RUST GATES
Signed-off-by: salmonik <raisersoft@proton.me>
Diffstat (limited to '')
-rw-r--r--cogs/vclogs.py316
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
6import datetime
7import logging
8import os
9import asyncio
10import discord
11from discord.ext import commands
12from gtts import gTTS
13from cogs.voice import get_voice_manager
14from cogs.utils.settings import get_volume, save_settings
15from cogs.utils.database import get_connection, init_database
16
17import tempfile
18
19logger = logging.getLogger(__name__)
20
21init_database()
22
23
24def 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
48def 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
77def 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
87class 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
315def setup(bot):
316 bot.add_cog(VcLogs(bot))