# Python client file (client.py) import argparse import os import platform import socketio import asyncio from discord import Intents, opus, Activity, ActivityType from discord.ext import commands from NoiseGatev2 import NoiseGate from debugger import setup_logger, running_dir import faulthandler faulthandler.enable() logger = setup_logger('main') # Example usage logger.info("Logging initialized successfully.") sio = socketio.AsyncClient() client = None device_id = None ng_threshold = None ### Core functions def load_opus(): logger.info(f"Running dir: '{running_dir}'") processor = platform.machine() logger.info(f"Processor: {processor}") logger.info(f'OS: {os.name}') if os.name == 'nt': opus_path = f'{running_dir}/opus/libopus_amd64.dll' if processor == "AMD64" else None else: opus_path = f'{running_dir}/opus/libopus_aarcch64.so' if processor == "aarch64" else f'{running_dir}/opus/libopus_armv7l.so' logger.debug(f"Opus path: '{opus_path}'") logger.info(f"Opus path: '{opus_path}'") if opus_path: opus.load_opus(opus_path) logger.info(f"Loaded OPUS library from {opus_path}") return True else: logger.error("Unsupported architecture or OS.") return False async def set_discord_presense(presense): # Set the presence of the bot (what it's listening to) await client.change_presence(activity=Activity(type=ActivityType.listening, name=presense)) async def join_voice_channel(channel_id): global device_id, ng_threshold channel = client.get_channel(int(channel_id)) logger.info(f"Joining voice channel {channel}") try: voice_connection = await channel.connect(timeout=60.0, reconnect=True) if opus.is_loaded(): logger.info("OPUS library loaded successfully") logger.info(f"Input index: {device_id}, Voice Connection: {voice_connection}, Channel: {channel}") stream_handler = NoiseGate( _input_device_index=device_id, _voice_connection=voice_connection, _noise_gate_threshold=ng_threshold ) stream_handler.run() logger.info("Audio stream started") return True else: logger.error("Failed to load OPUS library") return False except Exception as e: logging.error(e) logging.info('error encountered') async def leave_voice_channel(guild_id): guild = await client.fetch_guild(guild_id) logger.info(f"Leaving voice channel in guild {guild}") voice_client = guild.voice_client if voice_client: await voice_client.disconnect() logger.info("Disconnected from voice channel") else: logger.info("Not connected to any voice channel in the guild") # Check if the client needs to open if len(check_for_open_vc_connections()) <= 0: # Tell the server the client is going to close return False return True def check_for_open_vc_connections(): return client.voice_clients def check_if_discord_vc_connected(guild_id): if client: return any(int(vc.guild.id) == int(guild_id) for vc in client.voice_clients) return False async def get_discord_username(guild_id): try: guild = await client.fetch_guild(guild_id) member = await guild.fetch_member(get_discord_id()) if member.nick: print(f"Username: {member.nick}") return member.nick print(f"Username: {client.user.name if client.user else None}") return client.user.name if client.user else None except Exception as e: logging.warning(e) return None def check_if_client_is_open(): if client: return client.is_ready() return False def get_discord_id(): print(f"ID: {client.user.id if client.user else None}") return int(client.user.id) if client.user else None async def on_connect(): logger.info("Connected to WebSocket server") async def on_disconnect(): logger.info("Disconnected from WebSocket server") ### Socket Events @sio.event async def connect_error(): logger.error("Connection to WebSocket server failed") @sio.event async def set_system(data): logger.info(f"Received command to set system name (presense): {data['system']}") return await set_discord_presense(data['system']) @sio.event async def join_server(data): logger.info(f"Received command to join server: {data['channelId']}") return await join_voice_channel(data['channelId']) @sio.event async def leave_server(data): logger.info(f"Received command to leave server: {data['guildId']}") return await leave_voice_channel(data['guildId']) @sio.event async def check_discord_vc_connected(data): return check_if_discord_vc_connected(data['guildId']) @sio.event async def request_discord_username(data): return await get_discord_username(data['guildId']) @sio.event async def check_client_is_open(): return check_if_client_is_open() @sio.event async def request_discord_id(): return get_discord_id() @sio.event async def request_client_close(): exit() async def on_ready(): logger.info(f"We have logged in as {client.user}") logger.info("Loading OPUS library") load_opus() logger.info(opus.is_loaded()) if opus.is_loaded(): logger.info('Emitting to the server') await sio.emit('discord_ready', {'message': 'Discord bot is ready'}) async def start_bot(args): global client, device_id, ng_threshold # Connect to the WebSocket server await sio.connect('http://127.0.0.1:{}'.format(args.websocket_port), namespaces=['/']) logger.info("Connecting to WebSocket server...") intents = Intents.default() client = commands.Bot(command_prefix='!', intents=intents) device_id = args.device_id ng_threshold = args.ng_threshold client.add_listener(on_connect) client.add_listener(on_disconnect) client.add_listener(on_ready) await client.start(args.client_id) def parse_arguments(): parser = argparse.ArgumentParser() parser.add_argument("device_id", type=int, help="The ID of the audio device to use") parser.add_argument("client_id", type=str, help="The Discord client ID") parser.add_argument("websocket_port", type=int, help="The port of the WebSocket server") parser.add_argument("-n", "--ng_threshold", type=int, default=50, help="Change the noise gate threshold. Defaults to 50") return parser.parse_args() if __name__ == "__main__": try: args = parse_arguments() logger.info("Arguments: %s", args) # Create an event loop loop = asyncio.get_event_loop() # Run the start_bot function within the event loop loop.run_until_complete(start_bot(args)) except Exception as e: logger.error(e) logger.warning("Exiting now...")