From 45fee6d593e131ccdafe9eb55a26edd578eebdf2 Mon Sep 17 00:00:00 2001 From: timofey Date: Sat, 24 Dec 2022 08:02:22 +0000 Subject: [PATCH] commands revised + optimisations --- requirements.txt | 2 +- v6d3music/commands.py | 49 ++++++++++++------------ v6d3music/core/queueaudio.py | 5 ++- v6d3music/core/ystate.py | 2 +- v6d3music/core/ytaudio.py | 46 +++++++++++++++++------ v6d3music/html/main.js | 4 ++ v6d3music/run-bot.py | 71 ++++++++++++++++++++--------------- v6d3music/utils/argctx.py | 2 +- v6d3music/utils/tor_prefix.py | 2 +- 9 files changed, 113 insertions(+), 70 deletions(-) diff --git a/requirements.txt b/requirements.txt index cf11735..617e56d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ ptvp35 @ git+https://gitea.parrrate.ru/PTV/ptvp35.git@87ba808c2af1be87f4fbb9d9b3b97ba748cb9fae v6d0auth @ git+https://gitea.parrrate.ru/PTV/v6d0auth.git@324236f435c92756aefe22877a97a906c462ef2c v6d1tokens @ git+https://gitea.parrrate.ru/PTV/v6d1tokens.git@96567a0cb0c3cb60f20647518df5370df6dc6664 -v6d2ctx @ git+https://gitea.parrrate.ru/PTV/v6d2ctx.git@4a821aa168a83924934b2ab833d283226eb307bb +v6d2ctx @ git+https://gitea.parrrate.ru/PTV/v6d2ctx.git@d6d3b873de4e67cf41c6ce22c99f962c66e59f91 rainbowadn @ git+https://gitea.parrrate.ru/PTV/rainbowadn.git@e9fba7b064902ceedee0dd5578cb47030665a6aa adaas @ git+https://gitea.parrrate.ru/PTV/adaas.git@5e407a73be3fc52e66a91725996087d4d11522f2 diff --git a/v6d3music/commands.py b/v6d3music/commands.py index 4954359..56fa6e4 100644 --- a/v6d3music/commands.py +++ b/v6d3music/commands.py @@ -1,19 +1,24 @@ import shlex +from typing import Callable -from v6d2ctx.context import Context, Explicit, at -from v6d2ctx.lock_for import lock_for - +from v6d3music.core.entries_effects_for_args import * 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.core.entries_effects_for_args import default_effects, set_default_effects from v6d3music.utils.options_for_effects import options_for_effects 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 -@at('commands', 'help') +at_of: AtOf[str, command_type] = AtOf() +at, of = at_of() + + +@at('help') async def help_(ctx: Context, args: list[str]) -> None: match args: case []: @@ -22,8 +27,8 @@ async def help_(ctx: Context, args: list[str]) -> None: await ctx.reply(f'help for {name}: `{name} help`') -@at('commands', '/') -@at('commands', 'play') +@at('/') +@at('play') async def play(ctx: Context, args: list[str]) -> None: await catch( ctx, args, @@ -47,7 +52,7 @@ presets: {shlex.join(allowed_presets)} await ctx.reply('done') -@at('commands', 'skip') +@at('skip') async def skip(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -74,7 +79,7 @@ async def skip(ctx: Context, args: list[str]) -> None: await ctx.reply('done') -@at('commands', 'to') +@at('to') async def skip_to(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -94,7 +99,7 @@ async def skip_to(ctx: Context, args: list[str]) -> None: queue.queue[0].set_seconds(seconds) -@at('commands', 'effects') +@at('effects') async def effects_(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -117,7 +122,7 @@ async def effects_(ctx: Context, args: list[str]) -> None: yta.set_seconds(seconds) -@at('commands', 'default') +@at('default') async def default(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -144,7 +149,7 @@ async def default(ctx: Context, args: list[str]) -> None: await ctx.reply(f'effects set to `{effects}`') -@at('commands', 'repeat') +@at('repeat') async def repeat(ctx: Context, args: list[str]): match args: case []: @@ -164,7 +169,7 @@ async def repeat(ctx: Context, args: list[str]): queue.queue.insert(1, audio.copy()) -@at('commands', 'branch') +@at('branch') async def branch(ctx: Context, args: list[str]): match args: case ['-', effects]: @@ -191,8 +196,8 @@ async def branch(ctx: Context, args: list[str]): queue.queue.insert(1, audio) -@at('commands', '//') -@at('commands', 'queue') +@at('//') +@at('queue') async def queue_(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -224,7 +229,7 @@ async def queue_(ctx: Context, args: list[str]) -> None: raise Explicit('misformatted') -@at('commands', 'swap') +@at('swap') async def swap(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -240,7 +245,7 @@ async def swap(ctx: Context, args: list[str]) -> None: raise Explicit('misformatted') -@at('commands', 'move') +@at('move') async def move(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -256,7 +261,7 @@ async def move(ctx: Context, args: list[str]) -> None: raise Explicit('misformatted') -@at('commands', 'volume') +@at('volume') async def volume_(ctx: Context, args: list[str]) -> None: await catch( ctx, args, ''' @@ -272,17 +277,13 @@ async def volume_(ctx: Context, args: list[str]) -> None: raise Explicit('misformatted') -@at('commands', 'pause') +@at('pause') async def pause(ctx: Context, _args: list[str]) -> None: vc = await vc_for(ctx, create=False, force_play=False) vc.pause() -@at('commands', 'resume') +@at('resume') async def resume(ctx: Context, _args: list[str]) -> None: vc = await vc_for(ctx, create=False, force_play=True) vc.resume() - - -def register_commands(): - pass diff --git a/v6d3music/core/queueaudio.py b/v6d3music/core/queueaudio.py index e2e3b54..e2ccb72 100644 --- a/v6d3music/core/queueaudio.py +++ b/v6d3music/core/queueaudio.py @@ -125,7 +125,10 @@ class QueueAudio(discord.AudioSource): async def format(self) -> str: stream = StringIO() - for i, audio in enumerate(list(self.queue)): + for i, audio in enumerate(lst := list(self.queue)): + if i >= (n := 100): + stream.write(f'cutting queue at {n} results, {len(lst) - n} remaining.\n') + break stream.write(f'`[{i}]` `{audio.source_timecode()} / {audio.duration()}` {audio.description}\n') return stream.getvalue() diff --git a/v6d3music/core/ystate.py b/v6d3music/core/ystate.py index 9d79288..c4c36d5 100644 --- a/v6d3music/core/ystate.py +++ b/v6d3music/core/ystate.py @@ -53,7 +53,7 @@ class YState: async def result(self, entry: InfoCtx) -> YTAudio | None: try: return await create_ytaudio(self.ctx, entry) - except: + except Exception: if not entry.ignore: raise else: diff --git a/v6d3music/core/ytaudio.py b/v6d3music/core/ytaudio.py index 0514139..33312e9 100644 --- a/v6d3music/core/ytaudio.py +++ b/v6d3music/core/ytaudio.py @@ -1,21 +1,22 @@ import asyncio -import functools import random from typing import Optional import discord -from v6d2ctx.context import Explicit - from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio from v6d3music.core.real_url import real_url from v6d3music.utils.fill import FILL from v6d3music.utils.sparq import sparq from v6d3music.utils.tor_prefix import tor_prefix +from v6d2ctx.context import Explicit __all__ = ('YTAudio',) +semaphore = asyncio.BoundedSemaphore(5) + + class YTAudio(discord.AudioSource): source: FFmpegNormalAudio @@ -25,7 +26,7 @@ class YTAudio(discord.AudioSource): origin: str, description: str, options: Optional[str], - rby: discord.Member, + rby: discord.Member | None, already_read: int, tor: bool, /, @@ -45,6 +46,9 @@ class YTAudio(discord.AudioSource): self.loop = asyncio.get_running_loop() self.stop_at: int | None = stop_at + def _reduced_durations(self) -> dict[str, str]: + return {url: duration for url, duration in self._durations.items() if url == self.url} + def set_source_if_necessary(self): if not hasattr(self, 'source'): self.set_source() @@ -80,7 +84,7 @@ class YTAudio(discord.AudioSource): def schedule_duration_update(self): self.loop.call_soon_threadsafe(self._schedule_duration_update) - async def update_duration(self): + async def _update_duration(self): url: str = self.url if url in self._durations: return @@ -104,6 +108,10 @@ class YTAudio(discord.AudioSource): assert ap.stdout is not None self._durations[url] = (await ap.stdout.read()).decode().strip().split('.')[0] + async def update_duration(self): + async with semaphore: + await self._update_duration() + def duration(self) -> str: duration = self._durations.get(self.url) if duration is None: @@ -114,7 +122,7 @@ class YTAudio(discord.AudioSource): before_options = '' if 'https' in self.url: before_options += ( - '-reconnect 1 -reconnect_at_eof 0 -reconnect_streamed 1 -reconnect_delay_max 10 -copy_unknown' + '-reconnect 1 -reconnect_at_eof 0 -reconnect_streamed 1 -reconnect_delay_max 60 -copy_unknown' ) if self.already_read: before_options += ( @@ -154,33 +162,49 @@ class YTAudio(discord.AudioSource): return True elif permissions.manage_messages: return True + elif self.rby is None: + return False else: return self.rby == member - def hybernate(self): + def hybernate(self) -> dict: return { 'url': self.url, 'origin': self.origin, 'description': self.description, 'options': self.options, - 'rby': self.rby.id, + 'rby': None if self.rby is None else self.rby.id, 'already_read': self.already_read, 'tor': self.tor, 'stop_at': self.stop_at, + 'durations': self._reduced_durations(), } @classmethod - async def respawn(cls, guild: discord.Guild, respawn) -> 'YTAudio': - return YTAudio( + async def respawn(cls, guild: discord.Guild, respawn: dict) -> 'YTAudio': + member_id: int | None = respawn['rby'] + if member_id is None: + member = None + else: + member = guild.get_member(member_id) + if member is None: + try: + member = await guild.fetch_member(respawn['rby']) + guild._add_member(member) + except discord.NotFound: + member = None + audio = YTAudio( respawn['url'], respawn['origin'], respawn['description'], respawn['options'], - guild.get_member(respawn['rby']) or await guild.fetch_member(respawn['rby']), + member, respawn['already_read'], respawn.get('tor', False), stop_at=respawn.get('stop_at', None), ) + audio._durations |= respawn.get('durations', {}) + return audio async def regenerate(self): try: diff --git a/v6d3music/html/main.js b/v6d3music/html/main.js index c42bccf..7310cc3 100644 --- a/v6d3music/html/main.js +++ b/v6d3music/html/main.js @@ -165,6 +165,9 @@ const aUpdateQueueSetup = async (el) => { (async () => { while (true) { await sleep(2); + if (queue !== null && queue.queuejson.length > 100) { + await sleep(queue.queuejson.length / 100); + } const newQueue = await aQueue(); await aUpdateQueueOnce(newQueue, el); queue = newQueue; @@ -176,6 +179,7 @@ const aUpdateQueueSetup = async (el) => { if (queue !== null) { for (const audio of queue.queuejson) { audio.tce.innerText = audio.ts(); + break; } } } diff --git a/v6d3music/run-bot.py b/v6d3music/run-bot.py index e06f162..92553f4 100644 --- a/v6d3music/run-bot.py +++ b/v6d3music/run-bot.py @@ -1,19 +1,19 @@ -import time -import contextlib import asyncio +import contextlib import os import sys +import time import traceback import discord from v6d3music.app import MusicAppFactory, session_db -from v6d3music.commands import register_commands +from v6d3music.commands import of from v6d3music.config import prefix from v6d3music.core.cache_url import cache_db +from v6d3music.core.entries_effects_for_args import effects_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 -from v6d3music.core.entries_effects_for_args import effects_db from rainbowadn.instrument import Instrumentation from v6d1tokens.client import request_token @@ -24,7 +24,6 @@ from v6d2ctx.serve import serve loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) -register_commands() class MusicClient(discord.Client): @@ -56,35 +55,47 @@ client = MusicClient( vcs_restored = False +async def _restore_vc(guild: discord.Guild, vccid: int, vc_is_paused: bool) -> None: + channels = await guild.fetch_channels() + channel: discord.VoiceChannel + channel, = [ + ch for ch in + ( + chc for chc in channels + if + isinstance(chc, discord.VoiceChannel) + ) + if ch.id == vccid + ] + vp: discord.VoiceProtocol = await channel.connect() + assert isinstance(vp, discord.VoiceClient) + vc = vp + await main_for_raw_vc(vc, create=True, force_play=True) + if vc_is_paused: + vc.pause() + + +async def restore_vc(vcgid: int, vccid: int, vc_is_paused: bool) -> None: + try: + print(f'vc restoring {vcgid}') + guild: discord.Guild = await client.fetch_guild(vcgid) + async with lock_for(guild, 'not in a guild'): + await _restore_vc(guild, vccid, vc_is_paused) + except Exception as e: + print(f'vc {vcgid} {vccid} {vc_is_paused} failed', e) + else: + print(f'vc restored {vcgid} {vccid}') + + async def restore_vcs(): global vcs_restored vcs: list[tuple[int, int, bool]] = queue_db.get('vcs', []) try: + tasks = [] for vcgid, vccid, vc_is_paused in vcs: - try: - guild: discord.Guild = await client.fetch_guild(vcgid) - async with lock_for(guild, 'not in a guild'): - channels = await guild.fetch_channels() - channel: discord.VoiceChannel - channel, = [ - ch for ch in - ( - chc for chc in channels - if - isinstance(chc, discord.VoiceChannel) - ) - if ch.id == vccid - ] - vp: discord.VoiceProtocol = await channel.connect() - assert isinstance(vp, discord.VoiceClient) - vc = vp - await main_for_raw_vc(vc, create=True, force_play=True) - if vc_is_paused: - vc.pause() - except Exception as e: - print(f'vc {vcgid} {vccid} {vc_is_paused} failed', e) - else: - print(f'vc restored {vcgid} {vccid}') + tasks.append(asyncio.create_task(restore_vc(vcgid, vccid, vc_is_paused))) + for task in tasks: + await task finally: vcs_restored = True @@ -116,7 +127,7 @@ def message_allowed(message: discord.Message) -> bool: @client.event async def on_message(message: discord.Message) -> None: if message_allowed(message): - await handle_content(message, message.content, prefix, client) + await handle_content(of, message, message.content, prefix, client) async def save_queues(delay: bool): diff --git a/v6d3music/utils/argctx.py b/v6d3music/utils/argctx.py index c8f8019..e1ac56d 100644 --- a/v6d3music/utils/argctx.py +++ b/v6d3music/utils/argctx.py @@ -31,7 +31,7 @@ class UrlCtx: try: async for info in entries_for_url(self.url, self.tor): yield InfoCtx(info, self.effects, self.already_read, self.tor, self.ignore) - except: + except Exception: if not self.ignore: raise diff --git a/v6d3music/utils/tor_prefix.py b/v6d3music/utils/tor_prefix.py index 664f844..89529af 100644 --- a/v6d3music/utils/tor_prefix.py +++ b/v6d3music/utils/tor_prefix.py @@ -7,7 +7,7 @@ if (address := os.getenv('v6tor', None)) is not None: try: import socket address = socket.gethostbyname_ex(address)[2][0] - except: + except Exception: print('failed tor resolution') _tor_prefix = None else: