From 872271fc6f2b724cde0c4d971437dbaad7d41e66 Mon Sep 17 00:00:00 2001 From: timofey Date: Sun, 25 Dec 2022 02:55:21 +0000 Subject: [PATCH] servicing --- v6d3music/app.py | 4 ++ v6d3music/commands.py | 32 +++++++-------- v6d3music/core/create_ytaudio.py | 15 ++++--- v6d3music/core/mainaudio.py | 10 +++-- v6d3music/core/mainservice.py | 43 ++++++++++++++------ v6d3music/core/queueaudio.py | 54 +++++++++++++++++++++++--- v6d3music/core/real_url.py | 2 +- v6d3music/core/ystate.py | 7 ++-- v6d3music/core/yt_audios.py | 18 --------- v6d3music/core/ytaservicing.py | 11 ++++++ v6d3music/core/ytaudio.py | 22 +++++------ v6d3music/html/main.js | 2 +- v6d3music/processing/abstractrunner.py | 13 +++++++ v6d3music/processing/pool.py | 27 +++++++++++-- v6d3music/run-bot.py | 10 ++--- v6d3music/utils/argctx.py | 23 ++++++----- v6d3music/utils/catch.py | 2 + v6d3music/utils/effects_for_preset.py | 3 ++ v6d3music/utils/options_for_effects.py | 2 + 19 files changed, 201 insertions(+), 99 deletions(-) delete mode 100644 v6d3music/core/yt_audios.py create mode 100644 v6d3music/core/ytaservicing.py create mode 100644 v6d3music/processing/abstractrunner.py diff --git a/v6d3music/app.py b/v6d3music/app.py index 0ecdee2..1479c63 100644 --- a/v6d3music/app.py +++ b/v6d3music/app.py @@ -280,6 +280,10 @@ class MusicAppFactory(AppFactory): except Api.MisusedApi as e: return web.json_response(e.json(), status=404) + @routes.get('/whaturl/') + async def whaturl(request: web.Request) -> web.StreamResponse: + return web.json_response(str(request.url)) + class AppContext: def __init__(self, mainservice: MainService) -> None: diff --git a/v6d3music/commands.py b/v6d3music/commands.py index d6caa78..e7ffebf 100644 --- a/v6d3music/commands.py +++ b/v6d3music/commands.py @@ -3,19 +3,20 @@ from typing import Callable from v6d3music.core.default_effects import * from v6d3music.core.mainservice import MainService -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.catch import * +from v6d3music.utils.effects_for_preset import * +from v6d3music.utils.options_for_effects import * from v6d3music.utils.presets import allowed_presets from v6d2ctx.at_of import AtOf from v6d2ctx.context import Context, Explicit, command_type from v6d2ctx.lock_for import lock_for +__all__ = ('get_of',) -def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable[[str], command_type]: + +def get_of(mainservice: MainService) -> Callable[[str], command_type]: at_of: AtOf[str, command_type] = AtOf() at, of = at_of() @@ -47,7 +48,7 @@ def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable if len(ctx.message.attachments) > 1: raise Explicit('no more than one attachment') args = [ctx.message.attachments[0].url] + args - async for audio in yt_audios(mainservice.caching, defaulteffects, ctx, args): + async for audio in mainservice.yt_audios(ctx, args): queue.append(audio) await ctx.reply('done') @@ -136,12 +137,12 @@ def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable case ['none']: effects = None case []: - await ctx.reply(f'current default effects: {defaulteffects.get(ctx.guild.id)}') + await ctx.reply(f'current default effects: {mainservice.defaulteffects.get(ctx.guild.id)}') return case _: raise Explicit('misformatted') assert_admin(ctx.member) - await defaulteffects.set(ctx.guild.id, effects) + await mainservice.defaulteffects.set(ctx.guild.id, effects) await ctx.reply(f'effects set to `{effects}`') @at('repeat') @@ -155,14 +156,13 @@ def get_of(mainservice: MainService, defaulteffects: DefaultEffects) -> Callable raise Explicit('misformatted') assert_admin(ctx.member) queue = await mainservice.context(ctx, create=False, force_play=False).queue() - if not queue.queue: - raise Explicit('empty queue') - if n > 99: - raise Explicit('too long') - audio = queue.queue[0] - for _ in range(n): - queue.queue.insert(1, audio.copy()) - queue.update_sources() + queue.repeat(n) + + @at('shuffle') + async def shuffle(ctx: Context, args: list[str]): + assert_admin(ctx.member) + queue = await mainservice.context(ctx, create=False, force_play=False).queue() + queue.shuffle() @at('branch') async def branch(ctx: Context, args: list[str]): diff --git a/v6d3music/core/create_ytaudio.py b/v6d3music/core/create_ytaudio.py index 5f54f94..2873fb1 100644 --- a/v6d3music/core/create_ytaudio.py +++ b/v6d3music/core/create_ytaudio.py @@ -1,19 +1,18 @@ import string -from typing import Any, Optional - -from v6d2ctx.context import Context, Explicit, escape from v6d3music.core.real_url import real_url -from v6d3music.core.caching import Caching +from v6d3music.core.ytaservicing import * from v6d3music.core.ytaudio import YTAudio +from v6d3music.utils.argctx import InfoCtx 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.utils.argctx import InfoCtx + +from v6d2ctx.context import Context, Explicit, escape async def create_ytaudio( - caching: Caching, ctx: Context, it: InfoCtx + servicing: YTAServicing, ctx: Context, it: InfoCtx ) -> YTAudio: assert ctx.member is not None if it.effects: @@ -25,8 +24,8 @@ async def create_ytaudio( else: options = None return YTAudio( - caching, - await real_url(caching, it.info['url'], False, it.tor), + servicing, + await real_url(servicing.caching, it.info['url'], False, it.tor), it.info['url'], f'{escape(it.info.get("title", "unknown"))} `Rby` {ctx.member}', options, diff --git a/v6d3music/core/mainaudio.py b/v6d3music/core/mainaudio.py index c11fa91..c640baa 100644 --- a/v6d3music/core/mainaudio.py +++ b/v6d3music/core/mainaudio.py @@ -1,11 +1,13 @@ import discord -from v6d3music.core.caching import Caching -from v6d3music.core.queueaudio import QueueAudio +from v6d3music.core.ytaservicing import * +from v6d3music.core.queueaudio import * from v6d3music.utils.assert_admin import assert_admin from ptvp35 import * from v6d2ctx.context import Explicit +__all__ = ('MainAudio',) + class MainAudio(discord.PCMVolumeTransformer): def __init__(self, db: DbConnection, queue: QueueAudio, volume: float): @@ -23,5 +25,5 @@ class MainAudio(discord.PCMVolumeTransformer): await self.db.set(member.guild.id, volume) @classmethod - async def create(cls, caching: Caching, db: DbConnection, queues: DbConnection, guild: discord.Guild) -> 'MainAudio': - return cls(db, await QueueAudio.create(caching, queues, guild), volume=db.get(guild.id, 0.2)) + async def create(cls, servicing: YTAServicing, db: DbConnection, queues: DbConnection, guild: discord.Guild) -> 'MainAudio': + return cls(db, await QueueAudio.create(servicing, queues, guild), volume=db.get(guild.id, 0.2)) diff --git a/v6d3music/core/mainservice.py b/v6d3music/core/mainservice.py index 79239fd..bba932a 100644 --- a/v6d3music/core/mainservice.py +++ b/v6d3music/core/mainservice.py @@ -1,22 +1,33 @@ import asyncio import traceback from contextlib import AsyncExitStack +from typing import AsyncIterable, TypeVar import discord from v6d3music.config import myroot -from v6d3music.core.caching import Caching -from v6d3music.core.mainaudio import MainAudio -from v6d3music.core.queueaudio import QueueAudio +from v6d3music.core.caching import * +from v6d3music.core.default_effects import * +from v6d3music.core.mainaudio import * +from v6d3music.core.queueaudio import * +from v6d3music.core.ystate import * +from v6d3music.core.ytaservicing import * +from v6d3music.core.ytaudio import * +from v6d3music.processing.pool import * +from v6d3music.utils.argctx import * from ptvp35 import * from v6d2ctx.context import Context, Explicit from v6d2ctx.lock_for import lock_for +T = TypeVar('T') + class MainService: - def __init__(self, client: discord.Client) -> None: + def __init__(self, defaulteffects: DefaultEffects, client: discord.Client) -> None: + self.defaulteffects = defaulteffects self.client = client self.mains: dict[discord.Guild, MainAudio] = {} + self.restore_lock = asyncio.Lock() @staticmethod async def raw_vc_for_member(member: discord.Member) -> discord.VoiceClient: @@ -51,16 +62,18 @@ class MainService: return self.descriptor(create=create, force_play=force_play).context(ctx) async def create(self, guild: discord.Guild) -> MainAudio: - return await MainAudio.create(self.caching, self.__volumes, self.queues, guild) + return await MainAudio.create(self.__servicing, self.__volumes, self.__queues, guild) async def __aenter__(self) -> 'MainService': async with AsyncExitStack() as es: self.__volumes = await es.enter_async_context(DbFactory(myroot / 'volume.db', kvfactory=KVJson())) - self.queues = await es.enter_async_context(DbFactory(myroot / 'queue.db', kvfactory=KVJson())) - self.caching = await es.enter_async_context(Caching()) + self.__queues = await es.enter_async_context(DbFactory(myroot / 'queue.db', kvfactory=KVJson())) + self.__caching = await es.enter_async_context(Caching()) + self.__pool = await es.enter_async_context(Pool(5)) + self.__servicing = YTAServicing(self.__caching, self.__pool) self.__vcs_restored: asyncio.Future[None] = asyncio.Future() - self.__es = es.pop_all() self.__save_task = asyncio.create_task(self.save_daemon()) + self.__es = es.pop_all() return self async def __aexit__(self, exc_type, exc_val, exc_tb): @@ -83,10 +96,10 @@ class MainService: if vc.is_playing(): if vc.guild is not None and vc.channel is not None: vcs.append((vc.guild.id, vc.channel.id, vc.is_paused())) - self.queues.set_nowait('vcs', vcs) + self.__queues.set_nowait('vcs', vcs) async def save_commit(self) -> None: - await self.queues.commit() + await self.__queues.commit() async def _save_all(self, delay: bool, save_playing: bool) -> None: await self.save_queues(delay) @@ -152,7 +165,7 @@ class MainService: print(f'vc restored {vcgid} {vccid}') async def restore_vcs(self) -> None: - vcs: list[tuple[int, int, bool]] = self.queues.get('vcs', []) + vcs: list[tuple[int, int, bool]] = self.__queues.get('vcs', []) try: tasks = [] for vcgid, vccid, vc_is_paused in vcs: @@ -163,10 +176,16 @@ class MainService: self.__vcs_restored.set_result(None) async def restore(self) -> None: - async with lock_for('vcs_restored', '...'): + async with self.restore_lock: if not self.__vcs_restored.done(): await self.restore_vcs() + async def yt_audios(self, ctx: Context, args: list[str]) -> AsyncIterable[YTAudio]: + assert ctx.guild is not None + argctx = ArgCtx(self.defaulteffects.get(ctx.guild.id), args) + async for audio in YState(self.__servicing, self.__pool, ctx, argctx.sources).iterate(): + yield audio + class MainDescriptor: def __init__(self, service: MainService, *, create: bool, force_play: bool) -> None: diff --git a/v6d3music/core/queueaudio.py b/v6d3music/core/queueaudio.py index 46ccc47..5c5abc6 100644 --- a/v6d3music/core/queueaudio.py +++ b/v6d3music/core/queueaudio.py @@ -1,15 +1,21 @@ import asyncio +import random from collections import deque from io import StringIO +from typing import MutableSequence import discord -from v6d3music.core.caching import Caching +from v6d2ctx.context import Explicit +from v6d3music.core.ytaservicing import * from v6d3music.core.ytaudio import YTAudio from v6d3music.utils.assert_admin import assert_admin from v6d3music.utils.fill import FILL from ptvp35 import * +__all__ = ('QueueAudio',) + + PRE_SET_LENGTH = 24 @@ -30,12 +36,12 @@ class QueueAudio(discord.AudioSource): return @staticmethod - async def respawned(caching: Caching, db: DbConnection, guild: discord.Guild) -> list[YTAudio]: + async def respawned(servicing: YTAServicing, db: DbConnection, guild: discord.Guild) -> list[YTAudio]: respawned = [] try: for audio_respawn in db.get(guild.id, []): try: - respawned.append(await YTAudio.respawn(caching, guild, audio_respawn)) + respawned.append(await YTAudio.respawn(servicing, guild, audio_respawn)) except Exception as e: print('audio respawn failed', e) raise @@ -44,8 +50,8 @@ class QueueAudio(discord.AudioSource): return respawned @classmethod - async def create(cls, caching: Caching, db: DbConnection, guild: discord.Guild) -> 'QueueAudio': - return cls(db, guild, await cls.respawned(caching, db, guild)) + async def create(cls, servicing: YTAServicing, db: DbConnection, guild: discord.Guild) -> 'QueueAudio': + return cls(db, guild, await cls.respawned(servicing, db, guild)) async def save(self, delay: bool) -> None: hybernated = [] @@ -143,3 +149,41 @@ class QueueAudio(discord.AudioSource): import random audios = list(self.queue) return [await audio.pubjson(member) for audio, _ in zip(audios, range(limit))] + + def repeat(self, n: int) -> None: + if not self.queue: + raise Explicit('empty queue') + if n > 99: + raise Explicit('too long') + audio = self.queue[0] + for _ in range(n): + self.queue.insert(1, audio.copy()) + self.update_sources() + + def shuffle(self) -> None: + try: + random.shuffle(ForwardView(self.queue)) + except: + from traceback import print_exc + print_exc() + self.update_sources() + + +class ForwardView(MutableSequence[YTAudio]): + def __init__(self, sequence: MutableSequence[YTAudio]) -> None: + self.sequence = sequence + + def __len__(self) -> int: + return max(0, self.sequence.__len__() - 1) + + def __setitem__(self, index: int, value: YTAudio) -> None: + self.sequence.__setitem__(index + 1, value) + + def __getitem__(self, index: int) -> YTAudio: + return self.sequence.__getitem__(index + 1) + + def __delitem__(self, index: int | slice) -> None: + self.sequence.__delitem__(index) + + def insert(self, index: int, value: YTAudio) -> None: + self.sequence.insert(index, value) diff --git a/v6d3music/core/real_url.py b/v6d3music/core/real_url.py index 687d5e4..1a77fe7 100644 --- a/v6d3music/core/real_url.py +++ b/v6d3music/core/real_url.py @@ -1,7 +1,7 @@ import asyncio import os -from v6d3music.core.caching import Caching +from v6d3music.core.caching import * from v6d3music.utils.bytes_hash import bytes_hash from v6d3music.utils.tor_prefix import tor_prefix diff --git a/v6d3music/core/ystate.py b/v6d3music/core/ystate.py index 5bdbd6c..ccfcde3 100644 --- a/v6d3music/core/ystate.py +++ b/v6d3music/core/ystate.py @@ -3,6 +3,7 @@ from collections import deque from contextlib import AsyncExitStack from typing import AsyncIterable, Iterable +from v6d3music.core.ytaservicing import * from v6d3music.core.create_ytaudio import * from v6d3music.core.ytaudio import * from v6d3music.processing.pool import * @@ -14,8 +15,8 @@ __all__ = ('YState',) class YState: - def __init__(self, caching: Caching, pool: Pool, ctx: Context, sources: Iterable[UrlCtx]) -> None: - self.caching = caching + def __init__(self, servicing: YTAServicing, pool: Pool, ctx: Context, sources: Iterable[UrlCtx]) -> None: + self.servicing = servicing self.pool = pool self.ctx = ctx self.sources: deque[UrlCtx] = deque(sources) @@ -53,7 +54,7 @@ class YState: async def result(self, entry: InfoCtx) -> YTAudio | None: try: - return await create_ytaudio(self.caching, self.ctx, entry) + return await create_ytaudio(self.servicing, self.ctx, entry) except Exception: if not entry.ignore: raise diff --git a/v6d3music/core/yt_audios.py b/v6d3music/core/yt_audios.py deleted file mode 100644 index 221ab33..0000000 --- a/v6d3music/core/yt_audios.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import AsyncIterable - -from v6d3music.core.default_effects import * -from v6d3music.core.ystate import * -from v6d3music.core.ytaudio import * -from v6d3music.core.caching import Caching -from v6d3music.processing.pool import * -from v6d3music.utils.argctx import * - -from v6d2ctx.context import Context - - -async def yt_audios(caching: Caching, defaulteffects: DefaultEffects, ctx: Context, args: list[str]) -> AsyncIterable[YTAudio]: - assert ctx.guild is not None - argctx = ArgCtx(defaulteffects.get(ctx.guild.id), args) - async with Pool(5) as pool: - async for audio in YState(caching, pool, ctx, argctx.sources).iterate(): - yield audio diff --git a/v6d3music/core/ytaservicing.py b/v6d3music/core/ytaservicing.py new file mode 100644 index 0000000..bf26430 --- /dev/null +++ b/v6d3music/core/ytaservicing.py @@ -0,0 +1,11 @@ +from v6d3music.core.caching import * +from v6d3music.processing.abstractrunner import * + + +__all__ = ('YTAServicing',) + + +class YTAServicing: + def __init__(self, caching: Caching, runner: AbstractRunner) -> None: + self.caching = caching + self.runner = runner diff --git a/v6d3music/core/ytaudio.py b/v6d3music/core/ytaudio.py index 07fb36b..831b3ef 100644 --- a/v6d3music/core/ytaudio.py +++ b/v6d3music/core/ytaudio.py @@ -5,25 +5,22 @@ from typing import Optional import discord from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio from v6d3music.core.real_url import real_url -from v6d3music.core.caching import Caching from v6d3music.utils.fill import FILL from v6d3music.utils.sparq import sparq from v6d3music.utils.tor_prefix import tor_prefix +from v6d3music.core.ytaservicing import * from v6d2ctx.context import Explicit __all__ = ('YTAudio',) -semaphore = asyncio.BoundedSemaphore(5) - - class YTAudio(discord.AudioSource): source: FFmpegNormalAudio def __init__( self, - caching: Caching, + servicing: YTAServicing, url: str, origin: str, description: str, @@ -35,7 +32,7 @@ class YTAudio(discord.AudioSource): *, stop_at: int | None = None ): - self.caching = caching + self.servicing = servicing self.url = url self.origin = origin self.description = description @@ -112,8 +109,7 @@ class YTAudio(discord.AudioSource): self._durations[url] = (await ap.stdout.read()).decode().strip().split('.')[0] async def update_duration(self): - async with semaphore: - await self._update_duration() + await self.servicing.runner.run(self._update_duration()) def duration(self) -> str: duration = self._durations.get(self.url) @@ -184,7 +180,7 @@ class YTAudio(discord.AudioSource): } @classmethod - async def respawn(cls, caching: Caching, guild: discord.Guild, respawn: dict) -> 'YTAudio': + async def respawn(cls, servicing: YTAServicing, guild: discord.Guild, respawn: dict) -> 'YTAudio': member_id: int | None = respawn['rby'] if member_id is None: member = None @@ -197,7 +193,7 @@ class YTAudio(discord.AudioSource): except discord.NotFound: member = None audio = YTAudio( - caching, + servicing, respawn['url'], respawn['origin'], respawn['description'], @@ -213,7 +209,7 @@ class YTAudio(discord.AudioSource): async def regenerate(self): try: print(f'regenerating {self.origin}') - self.url = await real_url(self.caching, self.origin, True, self.tor) + self.url = await real_url(self.servicing.caching, self.origin, True, self.tor) if hasattr(self, 'source'): self.source.cleanup() self.set_source() @@ -232,7 +228,7 @@ class YTAudio(discord.AudioSource): def copy(self) -> 'YTAudio': return YTAudio( - self.caching, + self.servicing, self.url, self.origin, self.description, @@ -247,7 +243,7 @@ class YTAudio(discord.AudioSource): raise Explicit('already branched') self.stop_at = stop_at = self.already_read + 50 audio = YTAudio( - self.caching, + self.servicing, self.url, self.origin, self.description, diff --git a/v6d3music/html/main.js b/v6d3music/html/main.js index 7310cc3..475a409 100644 --- a/v6d3music/html/main.js +++ b/v6d3music/html/main.js @@ -166,7 +166,7 @@ const aUpdateQueueSetup = async (el) => { while (true) { await sleep(2); if (queue !== null && queue.queuejson.length > 100) { - await sleep(queue.queuejson.length / 100); + await sleep((queue.queuejson.length - 100) / 200); } const newQueue = await aQueue(); await aUpdateQueueOnce(newQueue, el); diff --git a/v6d3music/processing/abstractrunner.py b/v6d3music/processing/abstractrunner.py new file mode 100644 index 0000000..7d5605f --- /dev/null +++ b/v6d3music/processing/abstractrunner.py @@ -0,0 +1,13 @@ +from typing import Any, Coroutine, TypeVar +from abc import ABC, abstractmethod + +T = TypeVar('T') + + +__all__ = ('AbstractRunner',) + + +class AbstractRunner(ABC): + @abstractmethod + async def run(self, coro: Coroutine[Any, Any, T]) -> T: + raise NotImplementedError diff --git a/v6d3music/processing/pool.py b/v6d3music/processing/pool.py index c1e1a5c..6df9097 100644 --- a/v6d3music/processing/pool.py +++ b/v6d3music/processing/pool.py @@ -1,7 +1,7 @@ import asyncio -from typing import Union +from typing import Any, Coroutine, Generic, TypeVar, Union -from v6d3music.processing.yprocess import * +from .abstractrunner import AbstractRunner __all__ = ('Job', 'Pool', 'JobDescriptor',) @@ -164,7 +164,23 @@ class Working: return self.__worker.busy() -class Pool: +T = TypeVar('T') + + +class CoroJD(JobDescriptor, Generic[T]): + def __init__(self, coro: Coroutine[Any, Any, T]) -> None: + self.future = asyncio.Future() + self.coro = coro + + async def run(self) -> JobDescriptor | None: + try: + self.future.set_result(await self.coro) + except BaseException as e: + self.future.set_exception(e) + return None + + +class Pool(AbstractRunner): def __init__(self, workers: int) -> None: if workers < 1: raise ValueError('non-positive number of workers') @@ -203,6 +219,11 @@ class Pool: def workers(self) -> int: return self.__workers + async def run(self, coro: Coroutine[Any, Any, T]) -> T: + job = CoroJD(coro) + self.submit(job.wrap()) + return await job.future + class JDC: def __init__(self, descriptor: JobDescriptor, pool: Pool) -> None: diff --git a/v6d3music/run-bot.py b/v6d3music/run-bot.py index 54bd7e6..4715804 100644 --- a/v6d3music/run-bot.py +++ b/v6d3music/run-bot.py @@ -6,7 +6,7 @@ import time import discord from v6d3music.app import AppContext -from v6d3music.commands import get_of +from v6d3music.commands import * from v6d3music.config import prefix from v6d3music.core.caching import * from v6d3music.core.default_effects import * @@ -54,8 +54,8 @@ def message_allowed(message: discord.Message) -> bool: return guild_allowed(message.guild) -def register_handlers(mainservice: MainService, defaulteffects: DefaultEffects): - of = get_of(mainservice, defaulteffects) +def register_handlers(mainservice: MainService): + of = get_of(mainservice) @client.event async def on_message(message: discord.Message) -> None: @@ -138,11 +138,11 @@ def _db_ee() -> contextlib.ExitStack: async def main(): async with ( DefaultEffects() as defaulteffects, - MainService(client) as mainservice, + MainService(defaulteffects, client) as mainservice, AppContext(mainservice), ABlockMonitor(delta=0.5) ): - register_handlers(mainservice, defaulteffects) + register_handlers(mainservice) if 'guerilla' in sys.argv: from pathlib import Path tokenpath = Path('.token.txt') diff --git a/v6d3music/utils/argctx.py b/v6d3music/utils/argctx.py index e1ac56d..7ffce3b 100644 --- a/v6d3music/utils/argctx.py +++ b/v6d3music/utils/argctx.py @@ -4,6 +4,7 @@ from v6d3music.utils.effects_for_preset import effects_for_preset from v6d3music.utils.entries_for_url import entries_for_url from v6d3music.utils.options_for_effects import options_for_effects from v6d3music.utils.sparq import sparq +from v6d2ctx.context import Explicit __all__ = ('InfoCtx', 'UrlCtx', 'ArgCtx',) @@ -61,14 +62,16 @@ class ArgCtx: case [*args]: pass ctx.already_read = round(seconds / sparq(options_for_effects(effects))) - match args: - case ['tor', *args]: - ctx.tor = True - case [*args]: - pass - match args: - case ['ignore', *args]: - ctx.ignore = True - case [*args]: - pass + while True: + match args: + case ['tor', *args]: + if ctx.tor: + raise Explicit('duplicate tor') + ctx.tor = True + case ['ignore', *args]: + if ctx.ignore: + raise Explicit('duplicate ignore') + ctx.ignore = True + case [*args]: + break self.sources.append(ctx) diff --git a/v6d3music/utils/catch.py b/v6d3music/utils/catch.py index a5f7ccf..5ae6471 100644 --- a/v6d3music/utils/catch.py +++ b/v6d3music/utils/catch.py @@ -2,6 +2,8 @@ from typing import Iterable from v6d2ctx.context import Context, Implicit +__all__ = ('catch',) + async def catch(ctx: Context, args: list[str], reply: str, *catched: (Iterable[str] | str), attachments_ok=True): if ctx.message.attachments and attachments_ok: diff --git a/v6d3music/utils/effects_for_preset.py b/v6d3music/utils/effects_for_preset.py index 9eaea8f..935e8c7 100644 --- a/v6d3music/utils/effects_for_preset.py +++ b/v6d3music/utils/effects_for_preset.py @@ -3,6 +3,9 @@ from v6d2ctx.context import Explicit from v6d3music.utils.presets import presets +__all__ = ('effects_for_preset',) + + def effects_for_preset(preset: str) -> str: if preset in presets: return presets[preset] diff --git a/v6d3music/utils/options_for_effects.py b/v6d3music/utils/options_for_effects.py index b5fa780..b1ba041 100644 --- a/v6d3music/utils/options_for_effects.py +++ b/v6d3music/utils/options_for_effects.py @@ -1,6 +1,8 @@ import shlex from typing import Optional +__all__ = ('options_for_effects',) + def options_for_effects(effects: str | None) -> Optional[str]: return f'-af {shlex.quote(effects)}' if effects else None