import asyncio import functools from types import CoroutineType from typing import Any, Callable from discord import Interaction, Message, VoiceChannel, Member, VoiceClient from discord.player import FFmpegPCMAudio from heidi_constants import SOUNDDIR print("Debug: Importing heidi_helpers.py") # Checks ----------------------------------------------------------------------------------------- # 1. @enforce_channel(ID) is added to a function, which evaluates to decorate with the channel_id in its closure # 2. The function is passed to decorate(function), def enforce_channel(channel_id: int): """ Only run a function if called from the specified voice channel. """ def decorate(function: Callable[..., CoroutineType[Any, Any, None]]): @functools.wraps(function) async def wrapped(*args: *tuple[Interaction, *tuple[Any, ...]], **kwargs: int): """ Sends an interaction response if the interaction is not triggered from the heidi_spam channel. """ interaction: Interaction = args[0] # Do not call the decorated function if the channel_id doesn't match if not interaction.channel_id == channel_id: _ = await interaction.response.send_message("Heidi sagt: Geh in heidi_spam du dulli", ephemeral=True) return await function(*args, **kwargs) return wrapped return decorate # Reactions -------------------------------------------------------------------------------------- def create_author_based_message_predicate(names: list[str]) -> Callable[[Message], bool]: """ Create a predicate that determines if a message was written by a certain author. For usage with on_message_triggers. """ def handler(message: Message) -> bool: for name in names: if ( isinstance(message.author, Member) and message.author.nick is not None and name.lower() in message.author.nick.lower() ): return True return False return handler # Sounds ----------------------------------------------------------------------------------------- async def play_voice_line( interaction: Interaction | None, voice_channel: VoiceChannel, board: str, sound: str, ) -> None: """ Play a voice line in the specified channel. """ try: # Check if the file exists _ = open(f"{SOUNDDIR}/{board}/{sound}") except IOError: print(f"Error: Invalid soundfile {SOUNDDIR}/{board}/{sound}!") if interaction is not None: _ = await interaction.response.send_message( f'Heidi sagt: "{board}/{sound}" kanninich finden bruder', ephemeral=True ) return if interaction is not None: _ = await interaction.response.send_message(f'Heidi sagt: "{board}/{sound}"', ephemeral=True) # TODO: Normalize volume when playing audio_source: FFmpegPCMAudio = FFmpegPCMAudio(f"{SOUNDDIR}/{board}/{sound}") # only works from docker voice_client: VoiceClient = await voice_channel.connect() voice_client.play(audio_source) while voice_client.is_playing(): await asyncio.sleep(1) await voice_client.disconnect() async def play_voice_line_for_member( interaction: Interaction | None, member: Member, board: str, sound: str, ) -> None: """ Play a voice line in the member's current channel. """ # Member needs to be in voice channel to hear audio (Heidi needs to know the channel to join) if ( member is None or member.voice is None or member.voice.channel is None or not isinstance(member.voice.channel, VoiceChannel) ): print("User not in (valid) voice channel!") if interaction is not None: _ = await interaction.response.send_message("Heidi sagt: Komm in den Channel!", ephemeral=True) return voice_channel: VoiceChannel = member.voice.channel await play_voice_line(interaction, voice_channel, board, sound)