Update bot.py

This commit is contained in:
End 2026-03-12 13:50:26 -07:00
parent e4a741db72
commit 80ba304043
No known key found for this signature in database

96
bot.py
View file

@ -2,6 +2,7 @@ import asyncio
import json
import logging
import os
import shutil
from datetime import datetime, timedelta
from pathlib import Path
@ -25,15 +26,33 @@ with open("config.json", "r", encoding="utf-8") as f:
with open("voices.json", "r", encoding="utf-8") as f:
voices_data = json.load(f)
Path("audio_cache").mkdir(exist_ok=True)
data directory (set this in your deployment environment, e.g. Coolify)
DATA_DIR = os.getenv("DATA_DIR", "/app/data")
Path(DATA_DIR).mkdir(parents=True, exist_ok=True)
SETTINGS_FILE = "guild_settings.json"
SETTINGS_FILE = os.path.join(DATA_DIR, "guild_settings.json")
AUDIO_CACHE_DIR = os.path.join(DATA_DIR, "audio_cache")
Path
(AUDIO_CACHE_DIR).mkdir(parents=True, exist_ok=True)
_image_settings = "/app/guild_settings.json"
if not os.path.exists(SETTINGS_FILE) and os.path.exists(_image_settings):
try:
shutil.copy(_image_settings, SETTINGS_FILE)
logger.info("Migrated guild_settings.json into DATA_DIR")
except Exception as e:
logger.error(f"Failed to migrate settings: {e}")
def load_guild_settings() -> dict:
if os.path.exists(SETTINGS_FILE):
with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
try:
with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception as e:
logger.error(f"Failed to load settings file {SETTINGS_FILE}: {e}")
return {}
return {}
@ -44,8 +63,11 @@ def save_guild_settings():
"tts_channels": data.get("tts_channels", []),
"bound_vc": data.get("bound_vc", None),
}
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(saveable, f, indent=2)
try:
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(saveable, f, indent=2)
except Exception as e:
logger.error(f"Failed to write settings to {SETTINGS_FILE}: {e}")
guild_settings: dict = load_guild_settings()
@ -92,6 +114,7 @@ def find_voice(name: str):
def ffmpeg_options() -> dict:
# Use the config audio settings; ffmpeg filter string can be adjusted as needed.
return {
"options": (
f"-vn -b:a {config['audio']['bitrate']}k "
@ -126,12 +149,20 @@ async def cleanup(path: str, delay: int = 8):
async def ensure_connected(
guild: discord.Guild, member: discord.Member
) -> discord.VoiceClient | None:
if not member.voice or not member.voice.channel:
return None
gs = get_guild(guild.id)
bound = gs.get("bound_vc")
target = None
if bound:
target = discord.utils.get(guild.voice_channels, id=bound)
if target is None and member.voice and member.voice.channel:
target = member.voice.channel
else:
if not member.voice or not member.voice.channel:
return None
target = member.voice.channel
target = member.voice.channel
vc = voice_clients.get(guild.id)
if vc and vc.is_connected():
if vc.channel.id != target.id:
await vc.move_to(target)
@ -150,7 +181,8 @@ async def enqueue(
guild: discord.Guild, member: discord.Member, text: str, voice_id: str
):
q = tts_queues.setdefault(guild.id, asyncio.Queue())
await q.put((text, voice_id, member))
await q.put((text
, voice_id, member))
if q.qsize() == 1:
asyncio.create_task(queue_worker(guild))
@ -165,23 +197,29 @@ async def queue_worker(guild: discord.Guild):
vc = await ensure_connected(guild, member)
if vc is None:
await member.send(
"Could not join a voice channel. Make sure you are in a voice channel (or the bound VC if one is set)."
)
try:
await member.send(
"Could not join a voice channel. Make sure you are in a voice channel (or the bound VC if one is set)."
)
except Exception:
pass
q.task_done()
continue
last_activity[guild.id] = datetime.now()
ts = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
path = f"audio_cache/{guild.id}_{ts}.mp3"
path = os.path.join(AUDIO_CACHE_DIR, f"{guild.id}_{ts}.mp3")
ok = await generate_tts(text, voice_id, path)
if not ok:
await member.send(
"Failed to generate TTS audio. The TTS service may be unavailable."
)
try:
await member.send(
"Failed to generate TTS audio. The TTS service may be unavailable."
)
except Exception:
pass
q.task_done()
continue
@ -194,7 +232,10 @@ async def queue_worker(guild: discord.Guild):
asyncio.create_task(cleanup(path, delay=12))
except Exception as e:
logger.error(f"Playback error: {e}")
await member.send("Failed to play audio. Check that ffmpeg is installed.")
try:
await member.send("Failed to play audio. Check that ffmpeg is installed.")
except Exception:
pass
q.task_done()
@ -229,7 +270,10 @@ async def on_message(message: discord.Message):
return
if not message.author.voice or not message.author.voice.channel:
await message.reply("You need to be in a voice channel.", mention_author=False)
try:
await message.reply("You need to be in a voice channel.", mention_author=False)
except Exception:
pass
return
if len(text) > config["tts"]["max_length"]:
@ -254,7 +298,10 @@ async def on_voice_state_update(member: discord.Member, before, after):
vc = voice_clients.get(member.guild.id)
if vc and vc.is_connected():
if all(m.bot for m in vc.channel.members):
await vc.disconnect()
try:
await vc.disconnect()
except Exception:
pass
voice_clients.pop(member.guild.id, None)
last_activity.pop(member.guild.id, None)
logger.info(f"Auto-disconnected from {member.guild.name}")
@ -344,7 +391,6 @@ async def setup_vc(
await interaction.response.send_message(
"Provide a voice channel to bind to.", ephemeral=True
)
return
gs["bound_vc"] = channel.id
save_guild_settings()
@ -454,7 +500,10 @@ async def stop(interaction: discord.Interaction):
except Exception:
break
await vc.disconnect()
try:
await vc.disconnect()
except Exception:
pass
voice_clients.pop(gid, None)
last_activity.pop(gid, None)
@ -472,6 +521,7 @@ async def tts_enable(interaction: discord.Interaction):
)
return
enabled = tts_enabled.setdefault(interaction.guild_id, set())
if interaction.user.id in enabled:
await interaction.response.send_message(