diff --git a/v6d3music/commands.py b/v6d3music/commands.py new file mode 100644 index 0000000..7385474 --- /dev/null +++ b/v6d3music/commands.py @@ -0,0 +1,200 @@ +import shlex + +from v6d2ctx.context import Context, Explicit, at +from v6d2ctx.lock_for import lock_for + +from v6d3music.core.mainasrc import main_for, queue_for, vc_for +from v6d3music.core.yt_audios import yt_audios +from v6d3music.utils.assert_admin import assert_admin +from v6d3music.utils.catch import catch +from v6d3music.utils.effects_for_preset import effects_for_preset +from v6d3music.utils.options_for_effects import options_for_effects +from v6d3music.utils.presets import allowed_presets + + +@at('commands', 'help') +async def help_(ctx: Context, args: list[str]) -> None: + match args: + case []: + await ctx.reply('music bot') + case [name]: + await ctx.reply(f'help for {name}: `{name} help`') + + +@at('commands', '/') +@at('commands', 'play') +async def play(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, + f''' +`play ...args` +`play url [- effects] ...args` +`play url [+ preset] ...args` +presets: {shlex.join(allowed_presets)} +''', + (), 'help' + ) + async with lock_for(ctx.guild, 'not in a guild'): + queue = await queue_for(ctx, create=True) + async for audio in yt_audios(ctx, args): + queue.append(audio) + await ctx.reply('done') + + +@at('commands', 'skip') +async def skip(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, ''' +`skip [first] [last]` +''', 'help' + ) + match args: + case []: + queue = await queue_for(ctx, create=False) + queue.skip_at(0, ctx.member) + case [pos] if pos.isdecimal(): + pos = int(pos) + queue = await queue_for(ctx, create=False) + queue.skip_at(pos, ctx.member) + case [pos0, pos1] if pos0.isdecimal() and pos1.isdecimal(): + pos0, pos1 = int(pos0), int(pos1) + queue = await queue_for(ctx, create=False) + for i in range(pos0, pos1 + 1): + if not queue.skip_at(pos0, ctx.member): + pos0 += 1 + case _: + raise Explicit('misformatted') + await ctx.reply('done') + + +@at('commands', 'to') +async def skip_to(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, ''' +`to [[h]] [m] s` +''', 'help' + ) + match args: + case [h, m, s] if h.isdecimal() and m.isdecimal() and s.isdecimal(): + seconds = 3600 * int(h) + 60 * int(m) + int(s) + case [m, s] if m.isdecimal() and s.isdecimal(): + seconds = 60 * int(m) + int(s) + case [s] if s.isdecimal(): + seconds = int(s) + case _: + raise Explicit('misformatted') + queue = await queue_for(ctx, create=False) + queue.queue[0].set_seconds(seconds) + + +@at('commands', 'effects') +async def effects_(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, ''' +`effects - effects` +`effects + preset` +''', 'help' + ) + match args: + case ['-', effects]: + pass + case ['+', preset]: + effects = effects_for_preset(preset) + case _: + raise Explicit('misformatted') + assert_admin(ctx.member) + queue = await queue_for(ctx, create=False) + yta = queue.queue[0] + seconds = yta.source_seconds() + yta.options = options_for_effects(effects) + yta.set_seconds(seconds) + + +@at('commands', 'queue') +async def queue_(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, ''' +`queue` +`queue clear` +`queue resume` +`queue pause` +''', 'help' + ) + match args: + case []: + await ctx.long((await (await queue_for(ctx, create=False)).format()).strip() or 'no queue') + case ['clear']: + (await queue_for(ctx, create=False)).clear(ctx.member) + await ctx.reply('done') + case ['resume']: + async with lock_for(ctx.guild, 'not in a guild'): + await queue_for(ctx, create=True) + await ctx.reply('done') + case ['pause']: + async with lock_for(ctx.guild, 'not in a guild'): + vc = await vc_for(ctx, create=True) + vc.pause() + await ctx.reply('done') + case _: + raise Explicit('misformatted') + + +@at('commands', 'swap') +async def swap(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, ''' +`swap a b` +''', 'help' + ) + match args: + case [a, b] if a.isdecimal() and b.isdecimal(): + a, b = int(a), int(b) + (await queue_for(ctx, create=False)).swap(ctx.member, a, b) + case _: + raise Explicit('misformatted') + + +@at('commands', 'move') +async def move(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, ''' +`move a b` +''', 'help' + ) + match args: + case [a, b] if a.isdecimal() and b.isdecimal(): + a, b = int(a), int(b) + (await queue_for(ctx, create=False)).move(ctx.member, a, b) + case _: + raise Explicit('misformatted') + + +@at('commands', 'volume') +async def volume_(ctx: Context, args: list[str]) -> None: + await catch( + ctx, args, ''' +`volume volume` +''', 'help' + ) + match args: + case [volume]: + volume = float(volume) + await (await main_for(ctx, create=False)).set(volume, ctx.member) + case _: + raise Explicit('misformatted') + + +@at('commands', 'pause') +async def pause(ctx: Context, _args: list[str]) -> None: + vc = await vc_for(ctx, create=False) + vc.pause() + + +@at('commands', 'resume') +async def resume(ctx: Context, _args: list[str]) -> None: + vc = await vc_for(ctx, create=False) + vc.resume() + + +def register_commands(): + pass diff --git a/v6d3music/core/__init__.py b/v6d3music/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/v6d3music/cache_url.py b/v6d3music/core/cache_url.py similarity index 100% rename from v6d3music/cache_url.py rename to v6d3music/core/cache_url.py diff --git a/v6d3music/create_ytaudio.py b/v6d3music/core/create_ytaudio.py similarity index 91% rename from v6d3music/create_ytaudio.py rename to v6d3music/core/create_ytaudio.py index 9086769..20dba64 100644 --- a/v6d3music/create_ytaudio.py +++ b/v6d3music/core/create_ytaudio.py @@ -3,11 +3,11 @@ from typing import Any, Optional from v6d2ctx.context import Context, Explicit, escape -from v6d3music.real_url import real_url +from v6d3music.core.real_url import real_url +from v6d3music.core.ytaudio import YTAudio from v6d3music.utils.assert_admin import assert_admin from v6d3music.utils.options_for_effects import options_for_effects from v6d3music.utils.presets import allowed_effects -from v6d3music.ytaudio import YTAudio async def create_ytaudio( diff --git a/v6d3music/create_ytaudios.py b/v6d3music/core/create_ytaudios.py similarity index 83% rename from v6d3music/create_ytaudios.py rename to v6d3music/core/create_ytaudios.py index da52fb6..bc88909 100644 --- a/v6d3music/create_ytaudios.py +++ b/v6d3music/core/create_ytaudios.py @@ -3,9 +3,9 @@ from typing import AsyncIterable from v6d2ctx.context import Context -from v6d3music.create_ytaudio import create_ytaudio +from v6d3music.core.create_ytaudio import create_ytaudio +from v6d3music.core.ytaudio import YTAudio from v6d3music.utils.info_tuple import info_tuple -from v6d3music.ytaudio import YTAudio async def create_ytaudios(ctx: Context, infos: list[info_tuple]) -> AsyncIterable[YTAudio]: diff --git a/v6d3music/ffmpegnormalaudio.py b/v6d3music/core/ffmpegnormalaudio.py similarity index 100% rename from v6d3music/ffmpegnormalaudio.py rename to v6d3music/core/ffmpegnormalaudio.py diff --git a/v6d3music/core/mainasrc.py b/v6d3music/core/mainasrc.py new file mode 100644 index 0000000..ad50780 --- /dev/null +++ b/v6d3music/core/mainasrc.py @@ -0,0 +1,62 @@ +import discord +from v6d2ctx.context import Context, Explicit + +from v6d3music.core.mainaudio import MainAudio +from v6d3music.core.queueaudio import QueueAudio + +mainasrcs: dict[discord.Guild, MainAudio] = {} + + +async def raw_vc_for(ctx: Context) -> discord.VoiceClient: + if ctx.guild is None: + raise Explicit('not in a guild') + vc: discord.VoiceProtocol = ctx.guild.voice_client + if vc is None or isinstance(vc, discord.VoiceClient) and not vc.is_connected(): + vs: discord.VoiceState = ctx.member.voice + if vs is None: + raise Explicit('not connected') + vch: discord.VoiceChannel = vs.channel + if vch is None: + raise Explicit('not connected') + try: + vc: discord.VoiceProtocol = await vch.connect() + except discord.ClientException: + await ctx.guild.fetch_channels() + raise Explicit('try again later') + assert isinstance(vc, discord.VoiceClient) + return vc + + +async def main_for_raw_vc(vc: discord.VoiceClient, *, create: bool) -> MainAudio: + if vc.guild in mainasrcs: + source = mainasrcs[vc.guild] + else: + if create: + source = mainasrcs.setdefault( + vc.guild, + await MainAudio.create(vc.guild) + ) + else: + raise Explicit('not playing') + if vc.source != source or create and not vc.is_playing(): + vc.play(source) + return source + + +async def vc_main_for(ctx: Context, *, create: bool) -> tuple[discord.VoiceClient, MainAudio]: + vc = await raw_vc_for(ctx) + return vc, await main_for_raw_vc(vc, create=create) + + +async def vc_for(ctx: Context, *, create: bool) -> discord.VoiceClient: + vc, source = await vc_main_for(ctx, create=create) + return vc + + +async def main_for(ctx: Context, *, create: bool) -> MainAudio: + vc, source = await vc_main_for(ctx, create=create) + return source + + +async def queue_for(ctx: Context, *, create: bool) -> QueueAudio: + return (await main_for(ctx, create=create)).queue diff --git a/v6d3music/mainaudio.py b/v6d3music/core/mainaudio.py similarity index 94% rename from v6d3music/mainaudio.py rename to v6d3music/core/mainaudio.py index b9fa4a6..04a02e9 100644 --- a/v6d3music/mainaudio.py +++ b/v6d3music/core/mainaudio.py @@ -3,7 +3,7 @@ from ptvp35 import Db, KVJson from v6d2ctx.context import Explicit from v6d3music.config import myroot -from v6d3music.queueaudio import QueueAudio +from v6d3music.core.queueaudio import QueueAudio from v6d3music.utils.assert_admin import assert_admin volume_db = Db(myroot / 'volume.db', kvrequest_type=KVJson) diff --git a/v6d3music/queueaudio.py b/v6d3music/core/queueaudio.py similarity index 98% rename from v6d3music/queueaudio.py rename to v6d3music/core/queueaudio.py index f68af30..041761c 100644 --- a/v6d3music/queueaudio.py +++ b/v6d3music/core/queueaudio.py @@ -6,9 +6,9 @@ import discord from ptvp35 import Db, KVJson from v6d3music.config import myroot +from v6d3music.core.ytaudio import YTAudio from v6d3music.utils.assert_admin import assert_admin from v6d3music.utils.fill import FILL -from v6d3music.ytaudio import YTAudio queue_db = Db(myroot / 'queue.db', kvrequest_type=KVJson) diff --git a/v6d3music/real_url.py b/v6d3music/core/real_url.py similarity index 94% rename from v6d3music/real_url.py rename to v6d3music/core/real_url.py index bc91c7e..98278b7 100644 --- a/v6d3music/real_url.py +++ b/v6d3music/core/real_url.py @@ -4,8 +4,8 @@ from typing import Optional from v6d2ctx.context import Benchmark +from v6d3music.core.cache_url import cache_db, cache_url from v6d3music.utils.bytes_hash import bytes_hash -from v6d3music.cache_url import cache_db, cache_url async def real_url(url: str, override: bool, tor: bool) -> str: diff --git a/v6d3music/yt_audios.py b/v6d3music/core/yt_audios.py similarity index 86% rename from v6d3music/yt_audios.py rename to v6d3music/core/yt_audios.py index 0b481bf..4f4fc89 100644 --- a/v6d3music/yt_audios.py +++ b/v6d3music/core/yt_audios.py @@ -2,10 +2,10 @@ from typing import AsyncIterable from v6d2ctx.context import Context -from v6d3music.create_ytaudios import create_ytaudios +from v6d3music.core.create_ytaudios import create_ytaudios +from v6d3music.core.ytaudio import YTAudio from v6d3music.utils.entries_effects_for_args import entries_effects_for_args from v6d3music.utils.info_tuple import info_tuple -from v6d3music.ytaudio import YTAudio async def yt_audios(ctx: Context, args: list[str]) -> AsyncIterable[YTAudio]: diff --git a/v6d3music/ytaudio.py b/v6d3music/core/ytaudio.py similarity index 97% rename from v6d3music/ytaudio.py rename to v6d3music/core/ytaudio.py index 5ef61fb..f0a3c95 100644 --- a/v6d3music/ytaudio.py +++ b/v6d3music/core/ytaudio.py @@ -7,9 +7,9 @@ from typing import Optional import discord from v6d2ctx.context import Benchmark -from v6d3music.ffmpegnormalaudio import FFmpegNormalAudio +from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio +from v6d3music.core.real_url import real_url from v6d3music.utils.fill import FILL -from v6d3music.real_url import real_url from v6d3music.utils.sparq import sparq diff --git a/v6d3music/run-bot.py b/v6d3music/run-bot.py index 431a90d..60558b1 100644 --- a/v6d3music/run-bot.py +++ b/v6d3music/run-bot.py @@ -1,30 +1,26 @@ import asyncio import os -import shlex import subprocess import time import discord from v6d1tokens.client import request_token -from v6d2ctx.context import Benchmark, Context, Explicit, at, monitor +from v6d2ctx.context import Benchmark, monitor from v6d2ctx.handle_content import handle_content from v6d2ctx.lock_for import lock_for from v6d2ctx.serve import serve from v6d3music.app import MusicAppFactory, session_db -from v6d3music.cache_url import cache_db +from v6d3music.commands import register_commands from v6d3music.config import prefix -from v6d3music.mainaudio import MainAudio, volume_db -from v6d3music.queueaudio import QueueAudio, queue_db -from v6d3music.utils.assert_admin import assert_admin -from v6d3music.utils.catch import catch -from v6d3music.utils.effects_for_preset import effects_for_preset -from v6d3music.utils.options_for_effects import options_for_effects -from v6d3music.utils.presets import allowed_presets -from v6d3music.yt_audios import yt_audios +from v6d3music.core.cache_url import cache_db +from v6d3music.core.mainasrc import main_for_raw_vc, mainasrcs +from v6d3music.core.mainaudio import volume_db +from v6d3music.core.queueaudio import queue_db loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) +register_commands() client = discord.Client( intents=discord.Intents( members=True, @@ -36,6 +32,7 @@ client = discord.Client( guild_messages=True, reactions=True ), + loop=loop ) vcs_restored = False @@ -79,250 +76,9 @@ async def on_ready(): await restore_vcs() -@at('commands', 'help') -async def help_(ctx: Context, args: list[str]) -> None: - match args: - case []: - await ctx.reply('music bot') - case [name]: - await ctx.reply(f'help for {name}: `{name} help`') - - -mainasrcs: dict[discord.Guild, MainAudio] = {} - - -@at('commands', '/') -@at('commands', 'play') -async def play(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, - f''' -`play ...args` -`play url [- effects] ...args` -`play url [+ preset] ...args` -presets: {shlex.join(allowed_presets)} -''', - (), 'help' - ) - async with lock_for(ctx.guild, 'not in a guild'): - queue = await queue_for(ctx, create=True) - async for audio in yt_audios(ctx, args): - queue.append(audio) - await ctx.reply('done') - - -async def raw_vc_for(ctx: Context) -> discord.VoiceClient: - if ctx.guild is None: - raise Explicit('not in a guild') - vc: discord.VoiceProtocol = ctx.guild.voice_client - if vc is None or isinstance(vc, discord.VoiceClient) and not vc.is_connected(): - vs: discord.VoiceState = ctx.member.voice - if vs is None: - raise Explicit('not connected') - vch: discord.VoiceChannel = vs.channel - if vch is None: - raise Explicit('not connected') - try: - vc: discord.VoiceProtocol = await vch.connect() - except discord.ClientException: - await ctx.guild.fetch_channels() - raise Explicit('try again later') - assert isinstance(vc, discord.VoiceClient) - return vc - - -async def main_for_raw_vc(vc: discord.VoiceClient, *, create: bool) -> MainAudio: - if vc.guild in mainasrcs: - source = mainasrcs[vc.guild] - else: - if create: - source = mainasrcs.setdefault( - vc.guild, - await MainAudio.create(vc.guild) - ) - else: - raise Explicit('not playing') - if vc.source != source or create and not vc.is_playing(): - vc.play(source) - return source - - -async def vc_main_for(ctx: Context, *, create: bool) -> tuple[discord.VoiceClient, MainAudio]: - vc = await raw_vc_for(ctx) - return vc, await main_for_raw_vc(vc, create=create) - - -async def vc_for(ctx: Context, *, create: bool) -> discord.VoiceClient: - vc, source = await vc_main_for(ctx, create=create) - return vc - - -async def main_for(ctx: Context, *, create: bool) -> MainAudio: - vc, source = await vc_main_for(ctx, create=create) - return source - - -async def queue_for(ctx: Context, *, create: bool) -> QueueAudio: - return (await main_for(ctx, create=create)).queue - - -@at('commands', 'skip') -async def skip(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, ''' -`skip [first] [last]` -''', 'help' - ) - match args: - case []: - queue = await queue_for(ctx, create=False) - queue.skip_at(0, ctx.member) - case [pos] if pos.isdecimal(): - pos = int(pos) - queue = await queue_for(ctx, create=False) - queue.skip_at(pos, ctx.member) - case [pos0, pos1] if pos0.isdecimal() and pos1.isdecimal(): - pos0, pos1 = int(pos0), int(pos1) - queue = await queue_for(ctx, create=False) - for i in range(pos0, pos1 + 1): - if not queue.skip_at(pos0, ctx.member): - pos0 += 1 - case _: - raise Explicit('misformatted') - await ctx.reply('done') - - -@at('commands', 'to') -async def skip_to(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, ''' -`to [[h]] [m] s` -''', 'help' - ) - match args: - case [h, m, s] if h.isdecimal() and m.isdecimal() and s.isdecimal(): - seconds = 3600 * int(h) + 60 * int(m) + int(s) - case [m, s] if m.isdecimal() and s.isdecimal(): - seconds = 60 * int(m) + int(s) - case [s] if s.isdecimal(): - seconds = int(s) - case _: - raise Explicit('misformatted') - queue = await queue_for(ctx, create=False) - queue.queue[0].set_seconds(seconds) - - -@at('commands', 'effects') -async def effects_(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, ''' -`effects - effects` -`effects + preset` -''', 'help' - ) - match args: - case ['-', effects]: - pass - case ['+', preset]: - effects = effects_for_preset(preset) - case _: - raise Explicit('misformatted') - assert_admin(ctx.member) - queue = await queue_for(ctx, create=False) - yta = queue.queue[0] - seconds = yta.source_seconds() - yta.options = options_for_effects(effects) - yta.set_seconds(seconds) - - -@at('commands', 'queue') -async def queue_(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, ''' -`queue` -`queue clear` -`queue resume` -`queue pause` -''', 'help' - ) - match args: - case []: - await ctx.long((await (await queue_for(ctx, create=False)).format()).strip() or 'no queue') - case ['clear']: - (await queue_for(ctx, create=False)).clear(ctx.member) - await ctx.reply('done') - case ['resume']: - async with lock_for(ctx.guild, 'not in a guild'): - await queue_for(ctx, create=True) - await ctx.reply('done') - case ['pause']: - async with lock_for(ctx.guild, 'not in a guild'): - vc = await vc_for(ctx, create=True) - vc.pause() - await ctx.reply('done') - case _: - raise Explicit('misformatted') - - -@at('commands', 'swap') -async def swap(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, ''' -`swap a b` -''', 'help' - ) - match args: - case [a, b] if a.isdecimal() and b.isdecimal(): - a, b = int(a), int(b) - (await queue_for(ctx, create=False)).swap(ctx.member, a, b) - case _: - raise Explicit('misformatted') - - -@at('commands', 'move') -async def move(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, ''' -`move a b` -''', 'help' - ) - match args: - case [a, b] if a.isdecimal() and b.isdecimal(): - a, b = int(a), int(b) - (await queue_for(ctx, create=False)).move(ctx.member, a, b) - case _: - raise Explicit('misformatted') - - -@at('commands', 'volume') -async def volume_(ctx: Context, args: list[str]) -> None: - await catch( - ctx, args, ''' -`volume volume` -''', 'help' - ) - match args: - case [volume]: - volume = float(volume) - await (await main_for(ctx, create=False)).set(volume, ctx.member) - case _: - raise Explicit('misformatted') - - -@at('commands', 'pause') -async def pause(ctx: Context, _args: list[str]) -> None: - vc = await vc_for(ctx, create=False) - vc.pause() - - -@at('commands', 'resume') -async def resume(ctx: Context, _args: list[str]) -> None: - vc = await vc_for(ctx, create=False) - vc.resume() - - @client.event async def on_message(message: discord.Message) -> None: + print('on message') await handle_content(message, message.content, prefix)