diff --git a/README.md b/README.md index d7122f6..51ce252 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ ECIBot is a Discord bot made in Python. It's main purpose is to be a custom bot * `!confetti `: Plays the specified number of random Confetti songs. Alternative command: !co. ## TODO: -* [ ] Add more commands. -* [ ] Add tts messages to queue. -* [ ] Create log system. +* [X] Add more commands. +* [X] Add tts messages to queue. +* [X] Create log system. * [ ] Add more logs. * [ ] Add language support. * [ ] Create lang file. diff --git a/dalle.py b/dalle.py index 7120d1c..bdd734a 100644 --- a/dalle.py +++ b/dalle.py @@ -8,6 +8,7 @@ import requests from PIL import Image +from discord.abc import Messageable DALLE_FOLDER_PATH = "./dalle" @@ -18,9 +19,10 @@ class ResponseType(Enum): class DalleImages: - def __init__(self, response_type: ResponseType, image: Optional[str]): + def __init__(self, response_type: ResponseType, image: Optional[str], messageable: Messageable): self.__response_type = response_type self.__image = image + self.__messageable = messageable def get_response_type(self) -> ResponseType: return self.__response_type @@ -28,6 +30,9 @@ def get_response_type(self) -> ResponseType: def get_image(self) -> Optional[str]: return self.__image + def get_messageable(self) -> Messageable: + return self.__messageable + def check_dalle_dir(): if not os.path.exists(DALLE_FOLDER_PATH): @@ -49,7 +54,7 @@ def remove_image_from_memory(image_name: str): # Doing a similar image proccesing as in https://github.com/borisdayma/dalle-mini/blob/main/app/gradio/backend.py -def generate_images(text: str, listener: Callable[[DalleImages], Any]): +def generate_images(text: str, listener: Callable[[DalleImages], Any], messageable: Messageable): try: url = "https://bf.dallemini.ai/generate" data = {"prompt": text} @@ -61,19 +66,19 @@ def generate_images(text: str, listener: Callable[[DalleImages], Any]): images = json["images"] images = [Image.open(BytesIO(base64.b64decode(img.replace("\\n", "\n")))) for img in images] check_dalle_dir() - result = DalleImages(ResponseType.SUCCESS, generate_image_collage(images)) + result = DalleImages(ResponseType.SUCCESS, generate_image_collage(images), messageable) listener(result) elif response.status_code == 503: log.warning("generate_images >> Servicio 503, intentando de nuevo...") - generate_images(text, listener) + generate_images(text, listener, messageable) else: - listener(DalleImages(ResponseType.FAILURE, None)) + listener(DalleImages(ResponseType.FAILURE, None, messageable)) except Exception as e: log.error(f"generate_images >> Exception: {str(e)}") - listener(DalleImages(ResponseType.FAILURE, None)) + listener(DalleImages(ResponseType.FAILURE, None, messageable)) def generate_image_collage(images: list[Image]) -> str: diff --git a/guild_queue.py b/guild_queue.py new file mode 100644 index 0000000..01f4d5e --- /dev/null +++ b/guild_queue.py @@ -0,0 +1,137 @@ +import logging as log +from typing import Optional + +from discord import VoiceClient +from discord.abc import Messageable +from discord.channel import VocalGuildChannel +from discord.ext import tasks + +from tts import get_tts_dir_for_guild +from utils import remove_folder +from voice import Sound, stop_and_disconnect, SoundType, play_sound + + +class GuildQueue: + def __init__(self, guild_id: int, vocal_channel: VocalGuildChannel, messageable: Messageable, sound_queue: list[Sound]): + self.__guild_id = guild_id + self.__vocal_channel = vocal_channel + self.__messageable = messageable + self.__sound_queue = sound_queue + self.__voice_client = None + self.__finished = False + + def get_guild_id(self) -> int: + return self.__guild_id + + def get_vocal_channel(self) -> VocalGuildChannel: + return self.__vocal_channel + + def get_messageable(self) -> Messageable: + return self.__messageable + + def get_sound_queue(self) -> list[Sound]: + return self.__sound_queue + + def add_sound_to_queue(self, sound: Sound): + self.__sound_queue.append(sound) + + def clear_sound_queue(self): + self.__sound_queue.clear() + + def get_voice_client(self) -> Optional[VoiceClient]: + return self.__voice_client + + async def get_or_connect_to_voice_client(self) -> Optional[VoiceClient]: + if self.__voice_client is None: + self.__voice_client = await self.__vocal_channel.connect() + return self.__voice_client + + def has_finished(self) -> bool: + return self.__finished + + def set_finished(self, ready_for_removal: bool): + self.__finished = ready_for_removal + + +guild_queues: list[GuildQueue] = [] + + +def get_guild_queue(guild_id: int) -> Optional[GuildQueue]: + return next((gq for gq in guild_queues if gq.get_guild_id() == guild_id), None) + + +def add_new_guild_queue(guild_id: int, vocal_channel: VocalGuildChannel, messageable: Messageable) -> Optional[GuildQueue]: + guild_queue = GuildQueue(guild_id, vocal_channel, messageable, []) + if guild_queue is not None: + guild_queues.append(guild_queue) + return guild_queue + + +async def cleanup_guild_queues(): + not_finished_guild_queues = [] + for guild_queue in guild_queues: + if not guild_queue.has_finished(): + not_finished_guild_queues.append(guild_queue) + else: + await stop_and_disconnect(guild_queue.get_voice_client()) + remove_folder(get_tts_dir_for_guild(guild_queue.get_guild_id())) + + guild_queues.clear() + guild_queues.extend(not_finished_guild_queues) + if len(guild_queues) == 0: + queue_vitals.stop() + + +async def add_to_queue(guild_id: int, vocal_channel: VocalGuildChannel, messageable: Messageable, sound: Sound): + try: + guild_queue = get_guild_queue(guild_id) + should_start_vitals = False + + if guild_queue is None: + guild_queue = add_new_guild_queue(guild_id, vocal_channel, messageable) + should_start_vitals = True + else: + await messageable.send(f":notes: Añadido a la cola `{sound.get_name()}`.") + + if guild_queue is not None: + guild_queue.add_sound_to_queue(sound) + if should_start_vitals and not queue_vitals.is_running(): + queue_vitals.start() + + except Exception as e: + log.error("add_to_queue >> Exception thrown when adding sound to queue.", exc_info=e) + + +async def play_sounds_in_all_queues(): + for guild_queue in guild_queues: + try: + voice_client = await guild_queue.get_or_connect_to_voice_client() + if isinstance(voice_client, VoiceClient) and not voice_client.is_playing(): + sound_queue = guild_queue.get_sound_queue() + if len(sound_queue) == 0: + guild_queue.set_finished(True) + + else: + sound = sound_queue[0] + + if sound.get_sound_type() is SoundType.TTS: + await guild_queue.get_messageable().send(f":microphone: Reproduciendo un mensaje tts en `{voice_client.channel.name}`.") + elif sound.get_sound_type() is not SoundType.FILE_SILENT: + await guild_queue.get_messageable().send(f":notes: Reproduciendo `{sound.get_name()}` en `{voice_client.channel.name}`.") + + await play_sound(voice_client, sound) + sound_queue.pop(0) + + except Exception as e: + log.error("play_sounds_in_all_queues >> Caught exception playing sound. Removing sound queue for this guild...", exc_info=e) + sound_queue = guild_queue.get_sound_queue() + if len(sound_queue) > 0: + guild_queue.get_sound_queue().pop(0) + if len(sound_queue) == 0: + guild_queue.set_finished(True) + + +@tasks.loop(seconds=1, reconnect=True) +async def queue_vitals(): + await play_sounds_in_all_queues() + await cleanup_guild_queues() diff --git a/main.py b/main.py index 6c7701d..3b888ef 100644 --- a/main.py +++ b/main.py @@ -1,23 +1,25 @@ -import logging as log +import asyncio import random from datetime import datetime, time from threading import Event import discord -from discord import Message, Guild +from discord import Guild +from discord.abc import Messageable +from discord.channel import VocalGuildChannel from discord.ext import commands from discord.ext import tasks -from bd import Database -from dalle import ResponseType, generate_images, clear_dalle, remove_image_from_memory, DalleImages from ai import * -from text import TextChannel +from bd import Database +from dalle import ResponseType, generate_images, clear_dalle, remove_image_from_memory, DalleImages +from guild_queue import add_to_queue, get_guild_queue +from processors import process_link, process_reactions from threads import launch -from tts import generate_tts, clear_tts, tts_base_url +from tts import generate_tts from utils import * from voice import * from youtube import * -from processors import process_link, process_reactions MAX_RESPONSE_CHARACTERS = 2000 - 6 @@ -26,17 +28,14 @@ bot = commands.Bot(command_prefix='!', intents=intents, guild_subscriptions=True, fetch_offline_members=True) bot.remove_command("help") -sound_queue: list[Sound] = [] dalle_results_queue: list[DalleImages] = [] max_number = 10000 kiwi_chance = 500 dalle_event = Event() -tts_event = Event() @bot.event async def on_ready(): - log.info('{0.user} is alive!'.format(bot)) if is_debug_mode(): log.getLogger().setLevel(log.DEBUG) log.debug(">> Debug mode is ON") @@ -47,6 +46,7 @@ async def on_ready(): await bot.change_presence(activity=discord.Game("~bip-bop")) kiwi.start() + log.info(f"{bot.user} is alive!") sdos = bot.get_guild(689108711452442667) if sdos is not None: await sdos.me.edit(nick="Fran López") @@ -80,9 +80,7 @@ async def on_command_error(ctx: Context, exception: Exception): @bot.event async def on_error(event_name: str, *args, **kwargs): - if channel_text.get_text_channel() is not None: - await channel_text.get_text_channel().send(f"Ha ocurrido un error con {event_name}, {args}, {kwargs}.") - log.error(args) + log.error(f"on_error >> Error on event {event_name}, {args}, {kwargs}") @bot.event @@ -160,59 +158,67 @@ async def search(ctx: Context, arg: str): @bot.command(aliases=["p"], require_var_positional=True) async def play(ctx: Context, *args: str): - user_voice_channel = get_user_voice_channel(ctx) - if user_voice_channel is not None: + if await audio_play_prechecks(ctx.message): async for sound in generate_sounds(ctx, *args): database.register_user_interaction_play_sound(ctx.author.name, sound) - await add_to_queue(ctx, user_voice_channel, sound) - else: - await ctx.send("No estás en ningún canal conectado. :confused:") + await add_to_queue(ctx.guild.id, ctx.author.voice.channel, ctx.channel, sound) @bot.command(aliases=["decir", "t", "say"], require_var_positional=True) async def tts(ctx: Context, *args: str): text = " ".join(args) - user_voice_channel = get_user_voice_channel(ctx) - database.register_user_interaction(ctx.author.name, "tts") - - if user_voice_channel is not None: - channel_text.set_text_channel(ctx.channel) - voice_channel.set_voice_channel(user_voice_channel) - - await ctx.send(":tools::snail: Generando mensaje tts...") - launch(lambda: generate_tts(text, tts_listener)) + if await audio_play_prechecks(ctx.message): + await ctx.send(":tools::snail: Generando mensaje tts...") + database.register_user_interaction(ctx.author.name, "tts") + launch(lambda: generate_tts(text, ctx.guild.id, ctx.author.voice.channel, ctx.channel, tts_listener)) @bot.command(aliases=["q", "cola"]) async def queue(ctx: Context): - embed_msg = discord.Embed(title="Cola de sonidos", description=f"Actualmente hay {len(sound_queue)} sonidos en la cola.", color=0x01B05B) - database.register_user_interaction(ctx.author.name, "queue") + guild = ctx.guild + if guild is not None: + guild_queue = get_guild_queue(guild.id) + sound_queue = guild_queue.get_sound_queue() if guild_queue is not None else [] - if len(sound_queue) > 0: - sounds = map(lambda sound: sound.get_name(), sound_queue) - embed_msg.add_field(name="Sonidos en cola", value=", ".join(sounds), inline=False) + embed_msg = discord.Embed(title="Cola de sonidos", description=f"Actualmente hay {len(sound_queue)} sonidos en la cola de {guild.name}.", color=0x01B05B) + database.register_user_interaction(ctx.author.name, "queue") - await ctx.send(embed=embed_msg) + if len(sound_queue) > 0: + sounds = map(lambda sound: sound.get_name(), sound_queue) + embed_msg.add_field(name="Sonidos en cola", value=", ".join(sounds), inline=False) + + await ctx.send(embed=embed_msg) + else: + await ctx.send("No estás conectado a un servidor :angry:") @bot.command(aliases=["s"]) async def stop(ctx: Context): - for voice_client in bot.voice_clients: - if isinstance(voice_client, VoiceClient) and voice_client.guild == ctx.guild and voice_client.is_playing(): + guild = ctx.guild + if guild is not None: + voice_client = guild.voice_client + if isinstance(voice_client, VoiceClient): database.register_user_interaction(ctx.author.name, "stop") voice_client.stop() await ctx.send(":stop_button: Sonido parado.") - break + else: + await ctx.send("No estás conectado a un servidor :angry:") @bot.command(aliases=["dc"]) async def disconnect(ctx: Context): - for voice_client in bot.voice_clients: - if isinstance(voice_client, VoiceClient) and voice_client.guild == ctx.guild: + guild = ctx.guild + if guild is not None: + voice_client = guild.voice_client + if isinstance(voice_client, VoiceClient): database.register_user_interaction(ctx.author.name, "disconnect") - await clear_bot(voice_client) + guild_queue = get_guild_queue(guild.id) + if guild_queue is not None: + guild_queue.clear_sound_queue() + await stop_and_disconnect(voice_client) await ctx.send(":robot: Desconectando...") - break + else: + await ctx.send("No estás conectado a un servidor :angry:") @bot.command(aliases=["e", "encuesta"], require_var_positional=True) @@ -244,28 +250,23 @@ async def ask(ctx: Context, *, text: str = ""): @bot.command(aliases=["yt"], require_var_positional=True) async def youtube(ctx: Context, *args: str): search_query = " ".join(args) - user_voice_channel = get_user_voice_channel(ctx) database.register_user_interaction(ctx.author.name, "youtube") - if user_voice_channel is not None: - channel_text.set_text_channel(ctx.channel) + if await audio_play_prechecks(ctx.message): await ctx.send(f":clock10: Buscando `{search_query}` en YouTube...") yt_dlp_info = yt_search_and_extract_yt_dlp_info(search_query) if yt_dlp_info is not None: async for sound in generate_sounds_from_yt_dlp_info(ctx, yt_dlp_info): - await add_to_queue(ctx, user_voice_channel, sound) + await add_to_queue(ctx.guild.id, ctx.author.voice.channel, ctx.channel, sound) else: await ctx.send(":no_entry_sign: No se ha encontrado ningún contenido.") - else: - await ctx.send("No estás en ningún canal conectado. :confused:") @bot.command(aliases=["d"], require_var_positional=True) async def dalle(ctx: Context, *args: str): text = " ".join(args) database.register_user_interaction(ctx.author.name, "dalle") - channel_text.set_text_channel(ctx.channel) await ctx.send(":clock10: Generando imagen. Puede tardar varios minutos...") - launch(lambda: generate_images(text, dalle_listener)) + launch(lambda: generate_images(text, dalle_listener, ctx.channel)) @bot.command(aliases=["co"]) @@ -296,68 +297,14 @@ async def youtubemusic(ctx: Context, *args: str): await ctx.send(":no_entry_sign: No se ha encontrado ningún contenido.") -async def add_to_queue(ctx: Context, user_voice_channel: Optional[VoiceChannel], sound: Sound): - if user_voice_channel is not None: - channel_text.set_text_channel(ctx.channel) - if voice_channel.get_voice_client() is None: - client = await user_voice_channel.connect() - voice_channel.set_voice_client(client) - sound_queue.append(sound) - bot_vitals.start() - - else: - await ctx.send(f":notes: Añadido a la cola `{sound.get_name()}`.") - sound_queue.append(sound) - - def dalle_listener(result: DalleImages): dalle_results_queue.append(result) dalle_event.set() -def tts_listener(original_file: str): - filename = original_file.replace(tts_base_url, "").replace(".mp3", "") - sound = Sound(filename, SoundType.TTS, original_file) - sound_queue.append(sound) - tts_event.set() - - -@tasks.loop(seconds=1, reconnect=True) -async def bot_vitals(): - if voice_channel.get_voice_client() is None and voice_channel.get_voice_channel() is not None: - log.info("bot_vitals >> No hay ningún cliente conectado") - voice_client = await get_voice_client(voice_channel) - log.info(f"bot_vitals >> Conectando a {voice_client}...") - voice_channel.set_voice_client(voice_client) - - try: - for voice_client in bot.voice_clients: - if isinstance(voice_client, VoiceClient) and not voice_client.is_playing(): - if len(sound_queue) == 0: - await clear_bot(voice_client) - - else: - sound = sound_queue[0] - - if sound.get_sound_type() == SoundType.FILE_SILENT: - pass - elif sound.get_sound_type() == SoundType.TTS: - await channel_text.get_text_channel().send(f":microphone: Reproduciendo un mensaje tts en `{voice_client.channel.name}`.") - else: - await channel_text.get_text_channel().send(f":notes: Reproduciendo `{sound.get_name()}` en `{voice_client.channel.name}`.") - - await play_sound(voice_client, sound) - sound_queue.pop(0) - break - - else: - log.info("bot_vitals >> Parece que se ha cerrado la conexión de manera inesperada, limpiando la cola...") - await clear_bot(None) - - except Exception: - log.warning("bot_vitals >> Something happened, stopping bot_vitals.") - traceback.print_exc() - await clear_bot(None) +def tts_listener(guild_id: int, vocal_channel: VocalGuildChannel, messageable: Messageable, original_file: str): + sound = Sound("mensaje tts", SoundType.TTS, original_file) + asyncio.run_coroutine_threadsafe(add_to_queue(guild_id, vocal_channel, messageable, sound), bot.loop) @tasks.loop(minutes=1) @@ -392,13 +339,11 @@ async def kiwi(): sound_name = "ohvaya" if sound_name is not None: - voice_client = await eci_channel.connect() - voice_channel.set_voice_client(voice_client) - sound = Sound(sound_name, SoundType.FILE_SILENT, generate_audio_path(sound_name)) - log.info(f"kiwi >> Playing {sound_name}") database.register_user_interaction("kiwi", "kiwi", sound_name) - sound_queue.append(sound) - bot_vitals.start() + android_channel_messageable = bot.get_partial_messageable(857572212935360512) + sound = Sound(sound_name, SoundType.FILE_SILENT, generate_audio_path(sound_name)) + await add_to_queue(eci_channel.guild.id, eci_channel, android_channel_messageable, sound) + log.info(f"kiwi >> Added {sound_name} to queue") except discord.errors.ClientException as exc: log.error(">> Exception captured. Something happened at kiwi()", exc_info=exc) @@ -410,12 +355,12 @@ async def dalle_vitals(): log.info(f"dale_vitals >> Hay imágenes en la cola: {len(dalle_results_queue)} imágenes") if result.get_response_type() == ResponseType.SUCCESS: with open(result.get_image(), "rb") as image_file: - await channel_text.get_text_channel().send(":e_mail: Imagen recibida:", file=discord.File(image_file, filename="dalle.png")) + await result.get_messageable().send(":e_mail: Imagen recibida:", file=discord.File(image_file, filename="dalle.png")) remove_image_from_memory(result.get_image()) else: - await channel_text.get_text_channel().send(":confused: Ha ocurrido un error generando la imagen. Intenta de nuevo.") + await result.get_messageable().send(":confused: Ha ocurrido un error generando la imagen. Intenta de nuevo.") dalle_results_queue.remove(result) else: @@ -430,32 +375,8 @@ async def event_listener(): if not dalle_vitals.is_running(): dalle_vitals.start() - if tts_event.is_set(): - tts_event.clear() - if not bot_vitals.is_running(): - bot_vitals.start() - - -async def clear_bot(voice_client: Optional[VoiceClient]): - try: - if voice_client is not None: - voice_client.stop() - await voice_client.disconnect() - - except Exception: - log.warning(">> Exception captured. voice_client wasn't connected or something happened at clear_bot()") - traceback.print_exc() - - voice_channel.set_voice_client(None) - voice_channel.set_voice_channel(None) - bot_vitals.stop() - sound_queue.clear() - clear_tts() - if __name__ == "__main__": - channel_text = TextChannel() - voice_channel = CurrentVoiceChannel() database = Database(get_username_key(), get_password_key(), get_database_key()) openai_client = OpenAiClient(get_openai_key()) bot.run(get_bot_key()) diff --git a/text.py b/text.py deleted file mode 100644 index f056715..0000000 --- a/text.py +++ /dev/null @@ -1,9 +0,0 @@ -class TextChannel: - def __init__(self): - self.text_channel = None - - def get_text_channel(self): - return self.text_channel - - def set_text_channel(self, text_channel): - self.text_channel = text_channel diff --git a/threads.py b/threads.py index 86ec99d..7adf745 100644 --- a/threads.py +++ b/threads.py @@ -1,5 +1,6 @@ -from threading import Thread import logging as log +from threading import Thread + def launch(suspend_fun): try: diff --git a/tts.py b/tts.py index ed18dcf..2f9dac8 100644 --- a/tts.py +++ b/tts.py @@ -8,58 +8,56 @@ from urllib.request import urlretrieve import ffmpy +from discord.abc import Messageable +from discord.channel import VocalGuildChannel from gtts import gTTS -tts_base_url = "./tts/" +from utils import remove_file, check_dir +TTS_DIR_BASE_PATH = "./tts" -def check_base_dir(): - if not os.path.exists(tts_base_url): - os.makedirs(tts_base_url) +def get_tts_dir_for_guild(guild_id: int) -> str: + return os.path.normpath(f"{TTS_DIR_BASE_PATH}/{guild_id}") -def clear_tts(): - check_base_dir() - for file in os.listdir(tts_base_url): - os.remove(os.path.join(tts_base_url, file)) - -def get_loquendo_tts(text: str) -> Optional[str]: +def get_loquendo_tts(text: str, dir_path: str) -> Optional[str]: try: url_encoded_text = quote(text) - file_name = f"{tts_base_url}tts_{str(time.time())}.mp3" + file_path = os.path.normpath(f"{dir_path}/tts_{str(time.time())}.mp3") text_to_hash = f"262mp3{text}" hash_digest = hashlib.md5(text_to_hash.encode()).hexdigest() url = f"https://cache-a.oddcast.com/c_fs/{hash_digest}.mp3?engine=2&language=2&voice=6&text={url_encoded_text}&useUTF8=1" - urlretrieve(url, file_name) - return file_name + urlretrieve(url, file_path) + return file_path except Exception: log.warning("TTS >> There's an error with the Loquendo TTS, trying with Google tts") - return get_google_tts(text) + return get_google_tts(text, dir_path) -def get_google_tts(text: str) -> str: +def get_google_tts(text: str, dir_path: str) -> str: tts = gTTS(text=text, lang='es', tld='es') - file_name = f"{tts_base_url}tts_{str(time.time())}.mp3" - check_base_dir() - tts.save(file_name) + file_path = os.path.normpath(f"{dir_path}/tts_{str(time.time())}.mp3") + tts.save(file_path) speed = get_speed(text) - file = change_speed(file_name, speed) - return file + file_path = change_speed(file_path, dir_path, speed) + return file_path -def generate_tts(text: str, listener: Callable[[str], Any]): +def generate_tts(text: str, guild_id: int, vocal_channel: VocalGuildChannel, messageable: Messageable, listener: Callable[[int, VocalGuildChannel, Messageable, str], Any]): + tts_dir_path = get_tts_dir_for_guild(guild_id) + check_dir(tts_dir_path) if len(text) > 600: - audio = get_google_tts(text) + audio = get_google_tts(text, tts_dir_path) else: - audio = get_loquendo_tts(text) + audio = get_loquendo_tts(text, tts_dir_path) - listener(audio) + listener(guild_id, vocal_channel, messageable, audio) def get_speed(text: str) -> float: @@ -75,13 +73,14 @@ def get_speed(text: str) -> float: return 1.45 -def change_speed(file_name: str, speed: float) -> str: +def change_speed(file_path: str, dir_path: str, speed: float) -> str: try: - new_file_name = f"{tts_base_url}tts_{str(time.time())}.mp3" - ff = ffmpy.FFmpeg(inputs={file_name: None}, outputs={new_file_name: f"-filter:a atempo={speed}"}) + new_file_path = os.path.normpath(f"{dir_path}/tts_{str(time.time())}.mp3") + ff = ffmpy.FFmpeg(inputs={file_path: None}, outputs={new_file_path: f"-filter:a atempo={speed}"}) ff.run() - return new_file_name + remove_file(file_path) + return new_file_path - except Exception: - log.warning("TTS >> There's an error with the speed, trying with the original file") - return file_name + except Exception as e: + log.warning("TTS >> There's an error with the speed, trying with the original file", exc_info=e) + return file_path diff --git a/utils.py b/utils.py index d2c4cc7..ad22367 100644 --- a/utils.py +++ b/utils.py @@ -1,5 +1,7 @@ import json import os +import shutil +import traceback AUDIO_FOLDER_PATH = "./audio" @@ -78,5 +80,31 @@ def generate_sound_list_format(sounds: list[str]) -> list[list[str]]: return list_sounds +def check_dir(dir_path: str): + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + +def remove_file(path: str): + try: + if os.path.exists(path): + os.remove(path) + except Exception: + traceback.print_exc() + + +def remove_files(paths: list[str]): + for path in paths: + remove_file(path) + + +def remove_folder(dir_path: str): + try: + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + except Exception: + traceback.print_exc() + + if __name__ == "__main__": pass diff --git a/voice.py b/voice.py index 980d03a..1dce14f 100644 --- a/voice.py +++ b/voice.py @@ -2,14 +2,13 @@ import os import traceback from collections.abc import AsyncIterable +from enum import Enum from typing import Optional, Any -from discord import FFmpegPCMAudio, VoiceClient, VoiceChannel +from discord import FFmpegPCMAudio, VoiceClient, Member, Message from discord.ext.commands import Context from utils import AUDIO_FOLDER_PATH -from enum import Enum - from youtube import is_suitable_for_yt_dlp, extract_yt_dlp_info, MAX_PLAYLIST_ITEMS FFMPEG_OPTIONS_FOR_REMOTE_URL = { @@ -18,24 +17,6 @@ } -class CurrentVoiceChannel: - def __init__(self): - self.__voice_channel = None - self.__voice_client = None - - def get_voice_channel(self) -> Optional[VoiceChannel]: - return self.__voice_channel - - def set_voice_channel(self, voice_channel: Optional[VoiceChannel]): - self.__voice_channel = voice_channel - - def get_voice_client(self) -> Optional[VoiceClient]: - return self.__voice_client - - def set_voice_client(self, voice_client: Optional[VoiceClient]): - self.__voice_client = voice_client - - class SoundType(Enum): FILE = 0 FILE_SILENT = 1 @@ -59,6 +40,18 @@ def get_sound_type(self) -> SoundType: return self.__sound_type +async def audio_play_prechecks(message: Message) -> bool: + if message.guild is None or not isinstance(message.author, Member): + await message.channel.send("No estás conectado a un servidor :angry:") + return False + + if message.author.voice is None or message.author.voice.channel is None: + await message.channel.send("No estás en ningún canal conectado :confused:") + return False + + return True + + def generate_audio_path(name: str) -> str: return f"{AUDIO_FOLDER_PATH}/{name}.mp3" @@ -75,21 +68,14 @@ async def play_sound(client: Optional[VoiceClient], sound: Optional[Sound]): client.play(source=get_audio(sound)) -async def get_voice_client(voice_channel: CurrentVoiceChannel) -> VoiceClient: - if voice_channel.get_voice_client() is not None: - return voice_channel.get_voice_client() - - else: - return await voice_channel.get_voice_channel().connect() - - -def get_user_voice_channel(ctx: Context): +async def stop_and_disconnect(voice_client: Optional[VoiceClient]): try: - voice_state = ctx.message.author.voice - return voice_state.channel if voice_state is not None else None + if voice_client is not None: + voice_client.stop() + await voice_client.disconnect() except Exception: - log.error("get_user_voice_channel >> Exception thrown when getting voice channel from context.") + log.error("stop_and_disconnect >> Exception captured. voice_client wasn't connected or something happened.") traceback.print_exc()