commands revised + optimisations

This commit is contained in:
AF 2022-12-24 08:02:22 +00:00
parent 2e45ccc591
commit 45fee6d593
9 changed files with 113 additions and 70 deletions

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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:

View File

@ -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;
}
}
}

View File

@ -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):

View File

@ -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

View File

@ -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: