Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 42 additions & 15 deletions bot/audio_recorder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pathlib import Path
from typing import Dict, Optional
import logging
import queue

logger = logging.getLogger(__name__)

Expand All @@ -20,25 +21,21 @@ def __init__(self, meeting_id: str, output_dir: str = "recordings"):
self.sample_rate = 48000
self.channels = 2
self.sample_width = 2
self._queue = queue.Queue()
self._running = True

# Start background worker thread
self._thread = threading.Thread(target=self._worker, daemon=True)
self._thread.start()

logger.info(f"AudioSink initialized for meeting {meeting_id}")

def write(self, user, data):
if not data:
return

with self._lock:
user_id = user.id if hasattr(user, 'id') else user

if user_id not in self._user_writers:
username = user.name if hasattr(user, 'name') else f"user_{user_id}"
self._init_user_writer(user_id, username)

try:
writer = self._user_writers[user_id]
writer.writeframes(data)
except Exception as e:
logger.error(f"Error writing audio for user {user_id}: {e}")
user_id = user.id if hasattr(user, 'id') else user
self._queue.put((user_id, user, data))

def _init_user_writer(self, user_id: int, username: str):
safe_username = "".join(c for c in username if c.isalnum() or c in (' ', '-', '_'))
Expand All @@ -56,17 +53,47 @@ def _init_user_writer(self, user_id: int, username: str):
except Exception as e:
logger.error(f"Failed to create audio file for user {user_id}: {e}")

def _worker(self):
while self._running:
try:
item = self._queue.get()

if item is None:
break

user_id, user, data = item

# Get or create writer (LOCK ONLY FOR THIS PART)
with self._lock:
if user_id not in self._user_writers:
username = user.name if hasattr(user, 'name') else f"user_{user_id}"
self._init_user_writer(user_id, username)

writer = self._user_writers.get(user_id)

# WRITE OUTSIDE LOCK (IMPORTANT)
if writer:
writer.writeframes(data)

except Exception as e:
logger.error(f"Worker error: {e}")

def cleanup(self):
logger.info(f"Cleaning up audio sink for meeting {self.meeting_id}")

# Stop worker thread first
self._running = False
self._queue.put(None)
self._thread.join()

with self._lock:
logger.info(f"Cleaning up audio sink for meeting {self.meeting_id}")

for user_id, writer in self._user_writers.items():
try:
writer.close()
logger.info(f"Closed audio file for user {user_id}")
except Exception as e:
logger.error(f"Error closing audio file for user {user_id}: {e}")

self._user_writers.clear()

def get_recording_info(self) -> dict:
Expand Down
21 changes: 15 additions & 6 deletions bot/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import ssl
import certifi
import aiohttp
from discord.ext import commands

import discord
from discord import app_commands
from discord.ext import commands
Expand Down Expand Up @@ -348,28 +353,32 @@ async def end_meeting(interaction: discord.Interaction):
)


def main():
async def main():
token = os.getenv("DISCORD_BOT_TOKEN")

if not token:
logger.error("DISCORD_BOT_TOKEN not found in environment variables")
logger.error("Please create a .env file with your bot token")
sys.exit(1)

Path("recordings").mkdir(exist_ok=True)

ssl_context = ssl.create_default_context(cafile=certifi.where())
connector = aiohttp.TCPConnector(ssl=ssl_context)

try:
logger.info("Starting bot...")
bot.run(token, log_handler=None)

async with aiohttp.ClientSession(connector=connector) as session:
bot.http._HTTPClient__session = session # 🔥 inject SSL session
await bot.start(token)

except discord.LoginFailure:
logger.error("Invalid bot token")
sys.exit(1)
except KeyboardInterrupt:
logger.info("Bot stopped by user")
except Exception as e:
logger.error(f"Fatal error: {e}", exc_info=True)
sys.exit(1)


if __name__ == "__main__":
main()
asyncio.run(main())