diff --git a/arguments.py b/arguments.py index 7c96011..215eb4c 100644 --- a/arguments.py +++ b/arguments.py @@ -29,3 +29,11 @@ class ArgumentParser: await utils.reply(message, f"```\n{self.print_help()}```") except Exception as e: await utils.reply(message, f"`{e}`") + + +def range_type(string, min=0, max=100): + value = int(string) + if min <= value <= max: + return value + else: + raise argparse.ArgumentTypeError("value not in range %s-%s" % (min, max)) diff --git a/commands/bot.py b/commands/bot.py index 6fe922f..4a4527b 100644 --- a/commands/bot.py +++ b/commands/bot.py @@ -37,20 +37,24 @@ async def uptime(message): segments = [] duration = time.time() - start_time - if duration >= 86400: - d = int(duration // 86400) - segments.append(f"{d} {format_plural('day', d)}") - duration %= 86400 - if duration >= 3600: - h = int(duration // 3600) - segments.append(f"{h} {format_plural('hour', h)}") - duration %= 3600 - if duration >= 60: - m = int(duration // 60) - segments.append(f"{m} {format_plural('minute', m)}") - duration %= 60 - if duration > 0: - s = int(duration) - segments.append(f"{s} {format_plural('second', s)}") + + days, duration = divmod(duration, 86400) + if days >= 1: + days = int(days) + segments.append(f"{days} {format_plural('day', days)}") + + hours, duration = divmod(duration, 3600) + if hours >= 1: + hours = int(hours) + segments.append(f"{hours} {format_plural('hour', hours)}") + + minutes, duration = divmod(duration, 60) + if minutes >= 1: + minutes = int(minutes) + segments.append(f"{minutes} {format_plural('minute', minutes)}") + + seconds = int(duration) + if seconds > 0: + segments.append(f"{seconds} {format_plural('second', seconds)}") await utils.reply(message, f"up {', '.join(segments)}") diff --git a/commands/voice.py b/commands/voice.py index 74c0488..ce7f38f 100644 --- a/commands/voice.py +++ b/commands/voice.py @@ -1,9 +1,10 @@ -import arguments -import youtubedl -from state import client, player_queue +import functools +import arguments import commands import utils +import youtubedl +from state import client, player_current, player_queue async def queue_or_play(message): @@ -25,6 +26,14 @@ async def queue_or_play(message): action="store_true", help="clear all queued songs", ) + parser.add_argument( + "-v", + "--volume", + default=50, + type=functools.partial(arguments.range_type, min=0, max=150), + metavar="[0-150]", + help="the volume level (0 - 150)", + ) if not (args := await parser.parse_args(message, tokens)): return @@ -38,6 +47,7 @@ async def queue_or_play(message): player = await youtubedl.YTDLSource.from_url( query, loop=client.loop, stream=True ) + player.volume = float(args.volume) / 100.0 except Exception as e: await utils.reply( message, @@ -45,15 +55,16 @@ async def queue_or_play(message): ) return - player_queue[message.guild.id].append( - {"player": player, "queuer": message.author.id} + player_queue[message.guild.id].insert( + 0, {"player": player, "queuer": message.author.id} ) if ( not message.guild.voice_client.is_playing() and not message.guild.voice_client.is_paused() ): - await play_next(message) + await message.channel.send(f"**now playing:** `{player.title}`") + play_next(message) else: await utils.reply( message, @@ -69,7 +80,7 @@ async def queue_or_play(message): ) else: generate_currently_playing = ( - lambda: f"**0.** {'**paused:** ' if message.guild.voice_client.is_paused() else ''}`{message.guild.voice_client.source.title}`" + lambda: f"**0.** {'**paused:** ' if message.guild.voice_client.is_paused() else ''}`{message.guild.voice_client.source.title}` (<@{player_current[message.guild.id]['queuer']}>)" ) if ( not player_queue[message.guild.id] @@ -111,7 +122,8 @@ async def skip(message): "the queue is empty now!", ) else: - await play_next(message) + message.guild.voice_client.stop() + await utils.add_check_reaction(message) async def join(message): @@ -162,8 +174,7 @@ async def volume(message): parser.add_argument( "volume", nargs="?", - type=int, - choices=range(0, 151), + type=functools.partial(arguments.range_type, min=0, max=150), metavar="[0-150]", help="the volume level (0 - 150)", ) @@ -177,25 +188,24 @@ async def volume(message): ) return - if args.volume: - message.guild.voice_client.source.volume = float(args.volume) / 100.0 - await utils.add_check_reaction(message) - else: + if args.volume is None: await utils.reply( message, f"{int(message.guild.voice_client.source.volume * 100)}", ) + else: + message.guild.voice_client.source.volume = float(args.volume) / 100.0 + await utils.add_check_reaction(message) -async def play_next(message): - while player_queue[message.guild.id]: +def play_next(message, once=False): + message.guild.voice_client.stop() + if player_queue[message.guild.id]: queued = player_queue[message.guild.id].pop() - await ensure_joined(message) - message.guild.voice_client.stop() + player_current[message.guild.id] = queued message.guild.voice_client.play( - queued["player"], after=lambda e: print(f"player error: {e}") if e else None + queued["player"], after=lambda _: play_next(message) if not once else None ) - await message.channel.send(f"**now playing:** {queued['player'].title}") async def ensure_joined(message): diff --git a/constants.py b/constants.py index 66c283a..52c4a3e 100644 --- a/constants.py +++ b/constants.py @@ -6,12 +6,16 @@ PREFIX = "%" RELOADABLE_MODULES = [ "arguments", "commands", + "commands.bot", + "commands.tools", + "commands.utils", + "commands.voice", "constants", "core", "events", "utils", "voice", - "ytdlp", + "youtubedl", ] YTDL_OPTIONS = { diff --git a/core.py b/core.py index 4718981..08e5cd5 100644 --- a/core.py +++ b/core.py @@ -86,7 +86,7 @@ async def on_message(message): await utils.add_check_reaction(message) else: await message.channel.send(output) - case C.CLEAR | C.PURGE: + case C.CLEAR | C.PURGE if message.author.id in constants.OWNERS: await commands.tools.clear(message) case C.JOIN: await commands.voice.join(message) @@ -122,8 +122,10 @@ def rreload(reloaded_modules, module): with contextlib.suppress(AttributeError): for submodule in filter( lambda v: inspect.ismodule(v) - and (v.__name__.split(".")[-1] in constants.RELOADABLE_MODULES) + and v.__name__ in constants.RELOADABLE_MODULES and v.__name__ not in reloaded_modules, map(lambda attr: getattr(module, attr), dir(module)), ): rreload(reloaded_modules, submodule) + + importlib.reload(module) diff --git a/state.py b/state.py index 9766395..847e2a0 100644 --- a/state.py +++ b/state.py @@ -5,6 +5,7 @@ import disnake start_time = time.time() player_queue = {} +player_current = {} intents = disnake.Intents.default() intents.message_content = True diff --git a/youtubedl.py b/youtubedl.py index 59b5fde..ea2833f 100644 --- a/youtubedl.py +++ b/youtubedl.py @@ -35,7 +35,7 @@ class YTDLSource(disnake.PCMVolumeTransformer): return cls( disnake.FFmpegPCMAudio( data["url"] if stream else ytdl.prepare_filename(data), - options="-vn -reconnect 1", + before_options="-vn -reconnect 1", ), data=data, )