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 ptvp35 @ git+https://gitea.parrrate.ru/PTV/ptvp35.git@87ba808c2af1be87f4fbb9d9b3b97ba748cb9fae
v6d0auth @ git+https://gitea.parrrate.ru/PTV/v6d0auth.git@324236f435c92756aefe22877a97a906c462ef2c v6d0auth @ git+https://gitea.parrrate.ru/PTV/v6d0auth.git@324236f435c92756aefe22877a97a906c462ef2c
v6d1tokens @ git+https://gitea.parrrate.ru/PTV/v6d1tokens.git@96567a0cb0c3cb60f20647518df5370df6dc6664 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 rainbowadn @ git+https://gitea.parrrate.ru/PTV/rainbowadn.git@e9fba7b064902ceedee0dd5578cb47030665a6aa
adaas @ git+https://gitea.parrrate.ru/PTV/adaas.git@5e407a73be3fc52e66a91725996087d4d11522f2 adaas @ git+https://gitea.parrrate.ru/PTV/adaas.git@5e407a73be3fc52e66a91725996087d4d11522f2

View File

@ -1,19 +1,24 @@
import shlex import shlex
from typing import Callable
from v6d2ctx.context import Context, Explicit, at from v6d3music.core.entries_effects_for_args import *
from v6d2ctx.lock_for import lock_for
from v6d3music.core.mainasrc import main_for, queue_for, vc_for from v6d3music.core.mainasrc import main_for, queue_for, vc_for
from v6d3music.core.yt_audios import yt_audios from v6d3music.core.yt_audios import yt_audios
from v6d3music.utils.assert_admin import assert_admin from v6d3music.utils.assert_admin import assert_admin
from v6d3music.utils.catch import catch from v6d3music.utils.catch import catch
from v6d3music.utils.effects_for_preset import effects_for_preset 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.options_for_effects import options_for_effects
from v6d3music.utils.presets import allowed_presets 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: async def help_(ctx: Context, args: list[str]) -> None:
match args: match args:
case []: case []:
@ -22,8 +27,8 @@ async def help_(ctx: Context, args: list[str]) -> None:
await ctx.reply(f'help for {name}: `{name} help`') await ctx.reply(f'help for {name}: `{name} help`')
@at('commands', '/') @at('/')
@at('commands', 'play') @at('play')
async def play(ctx: Context, args: list[str]) -> None: async def play(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ctx, args,
@ -47,7 +52,7 @@ presets: {shlex.join(allowed_presets)}
await ctx.reply('done') await ctx.reply('done')
@at('commands', 'skip') @at('skip')
async def skip(ctx: Context, args: list[str]) -> None: async def skip(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -74,7 +79,7 @@ async def skip(ctx: Context, args: list[str]) -> None:
await ctx.reply('done') await ctx.reply('done')
@at('commands', 'to') @at('to')
async def skip_to(ctx: Context, args: list[str]) -> None: async def skip_to(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -94,7 +99,7 @@ async def skip_to(ctx: Context, args: list[str]) -> None:
queue.queue[0].set_seconds(seconds) queue.queue[0].set_seconds(seconds)
@at('commands', 'effects') @at('effects')
async def effects_(ctx: Context, args: list[str]) -> None: async def effects_(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -117,7 +122,7 @@ async def effects_(ctx: Context, args: list[str]) -> None:
yta.set_seconds(seconds) yta.set_seconds(seconds)
@at('commands', 'default') @at('default')
async def default(ctx: Context, args: list[str]) -> None: async def default(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -144,7 +149,7 @@ async def default(ctx: Context, args: list[str]) -> None:
await ctx.reply(f'effects set to `{effects}`') await ctx.reply(f'effects set to `{effects}`')
@at('commands', 'repeat') @at('repeat')
async def repeat(ctx: Context, args: list[str]): async def repeat(ctx: Context, args: list[str]):
match args: match args:
case []: case []:
@ -164,7 +169,7 @@ async def repeat(ctx: Context, args: list[str]):
queue.queue.insert(1, audio.copy()) queue.queue.insert(1, audio.copy())
@at('commands', 'branch') @at('branch')
async def branch(ctx: Context, args: list[str]): async def branch(ctx: Context, args: list[str]):
match args: match args:
case ['-', effects]: case ['-', effects]:
@ -191,8 +196,8 @@ async def branch(ctx: Context, args: list[str]):
queue.queue.insert(1, audio) queue.queue.insert(1, audio)
@at('commands', '//') @at('//')
@at('commands', 'queue') @at('queue')
async def queue_(ctx: Context, args: list[str]) -> None: async def queue_(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -224,7 +229,7 @@ async def queue_(ctx: Context, args: list[str]) -> None:
raise Explicit('misformatted') raise Explicit('misformatted')
@at('commands', 'swap') @at('swap')
async def swap(ctx: Context, args: list[str]) -> None: async def swap(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -240,7 +245,7 @@ async def swap(ctx: Context, args: list[str]) -> None:
raise Explicit('misformatted') raise Explicit('misformatted')
@at('commands', 'move') @at('move')
async def move(ctx: Context, args: list[str]) -> None: async def move(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -256,7 +261,7 @@ async def move(ctx: Context, args: list[str]) -> None:
raise Explicit('misformatted') raise Explicit('misformatted')
@at('commands', 'volume') @at('volume')
async def volume_(ctx: Context, args: list[str]) -> None: async def volume_(ctx: Context, args: list[str]) -> None:
await catch( await catch(
ctx, args, ''' ctx, args, '''
@ -272,17 +277,13 @@ async def volume_(ctx: Context, args: list[str]) -> None:
raise Explicit('misformatted') raise Explicit('misformatted')
@at('commands', 'pause') @at('pause')
async def pause(ctx: Context, _args: list[str]) -> None: async def pause(ctx: Context, _args: list[str]) -> None:
vc = await vc_for(ctx, create=False, force_play=False) vc = await vc_for(ctx, create=False, force_play=False)
vc.pause() vc.pause()
@at('commands', 'resume') @at('resume')
async def resume(ctx: Context, _args: list[str]) -> None: async def resume(ctx: Context, _args: list[str]) -> None:
vc = await vc_for(ctx, create=False, force_play=True) vc = await vc_for(ctx, create=False, force_play=True)
vc.resume() vc.resume()
def register_commands():
pass

View File

@ -125,7 +125,10 @@ class QueueAudio(discord.AudioSource):
async def format(self) -> str: async def format(self) -> str:
stream = StringIO() 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') stream.write(f'`[{i}]` `{audio.source_timecode()} / {audio.duration()}` {audio.description}\n')
return stream.getvalue() return stream.getvalue()

View File

@ -53,7 +53,7 @@ class YState:
async def result(self, entry: InfoCtx) -> YTAudio | None: async def result(self, entry: InfoCtx) -> YTAudio | None:
try: try:
return await create_ytaudio(self.ctx, entry) return await create_ytaudio(self.ctx, entry)
except: except Exception:
if not entry.ignore: if not entry.ignore:
raise raise
else: else:

View File

@ -1,21 +1,22 @@
import asyncio import asyncio
import functools
import random import random
from typing import Optional from typing import Optional
import discord import discord
from v6d2ctx.context import Explicit
from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio
from v6d3music.core.real_url import real_url from v6d3music.core.real_url import real_url
from v6d3music.utils.fill import FILL from v6d3music.utils.fill import FILL
from v6d3music.utils.sparq import sparq from v6d3music.utils.sparq import sparq
from v6d3music.utils.tor_prefix import tor_prefix from v6d3music.utils.tor_prefix import tor_prefix
from v6d2ctx.context import Explicit
__all__ = ('YTAudio',) __all__ = ('YTAudio',)
semaphore = asyncio.BoundedSemaphore(5)
class YTAudio(discord.AudioSource): class YTAudio(discord.AudioSource):
source: FFmpegNormalAudio source: FFmpegNormalAudio
@ -25,7 +26,7 @@ class YTAudio(discord.AudioSource):
origin: str, origin: str,
description: str, description: str,
options: Optional[str], options: Optional[str],
rby: discord.Member, rby: discord.Member | None,
already_read: int, already_read: int,
tor: bool, tor: bool,
/, /,
@ -45,6 +46,9 @@ class YTAudio(discord.AudioSource):
self.loop = asyncio.get_running_loop() self.loop = asyncio.get_running_loop()
self.stop_at: int | None = stop_at 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): def set_source_if_necessary(self):
if not hasattr(self, 'source'): if not hasattr(self, 'source'):
self.set_source() self.set_source()
@ -80,7 +84,7 @@ class YTAudio(discord.AudioSource):
def schedule_duration_update(self): def schedule_duration_update(self):
self.loop.call_soon_threadsafe(self._schedule_duration_update) self.loop.call_soon_threadsafe(self._schedule_duration_update)
async def update_duration(self): async def _update_duration(self):
url: str = self.url url: str = self.url
if url in self._durations: if url in self._durations:
return return
@ -104,6 +108,10 @@ class YTAudio(discord.AudioSource):
assert ap.stdout is not None assert ap.stdout is not None
self._durations[url] = (await ap.stdout.read()).decode().strip().split('.')[0] 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: def duration(self) -> str:
duration = self._durations.get(self.url) duration = self._durations.get(self.url)
if duration is None: if duration is None:
@ -114,7 +122,7 @@ class YTAudio(discord.AudioSource):
before_options = '' before_options = ''
if 'https' in self.url: if 'https' in self.url:
before_options += ( 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: if self.already_read:
before_options += ( before_options += (
@ -154,33 +162,49 @@ class YTAudio(discord.AudioSource):
return True return True
elif permissions.manage_messages: elif permissions.manage_messages:
return True return True
elif self.rby is None:
return False
else: else:
return self.rby == member return self.rby == member
def hybernate(self): def hybernate(self) -> dict:
return { return {
'url': self.url, 'url': self.url,
'origin': self.origin, 'origin': self.origin,
'description': self.description, 'description': self.description,
'options': self.options, 'options': self.options,
'rby': self.rby.id, 'rby': None if self.rby is None else self.rby.id,
'already_read': self.already_read, 'already_read': self.already_read,
'tor': self.tor, 'tor': self.tor,
'stop_at': self.stop_at, 'stop_at': self.stop_at,
'durations': self._reduced_durations(),
} }
@classmethod @classmethod
async def respawn(cls, guild: discord.Guild, respawn) -> 'YTAudio': async def respawn(cls, guild: discord.Guild, respawn: dict) -> 'YTAudio':
return 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['url'],
respawn['origin'], respawn['origin'],
respawn['description'], respawn['description'],
respawn['options'], respawn['options'],
guild.get_member(respawn['rby']) or await guild.fetch_member(respawn['rby']), member,
respawn['already_read'], respawn['already_read'],
respawn.get('tor', False), respawn.get('tor', False),
stop_at=respawn.get('stop_at', None), stop_at=respawn.get('stop_at', None),
) )
audio._durations |= respawn.get('durations', {})
return audio
async def regenerate(self): async def regenerate(self):
try: try:

View File

@ -165,6 +165,9 @@ const aUpdateQueueSetup = async (el) => {
(async () => { (async () => {
while (true) { while (true) {
await sleep(2); await sleep(2);
if (queue !== null && queue.queuejson.length > 100) {
await sleep(queue.queuejson.length / 100);
}
const newQueue = await aQueue(); const newQueue = await aQueue();
await aUpdateQueueOnce(newQueue, el); await aUpdateQueueOnce(newQueue, el);
queue = newQueue; queue = newQueue;
@ -176,6 +179,7 @@ const aUpdateQueueSetup = async (el) => {
if (queue !== null) { if (queue !== null) {
for (const audio of queue.queuejson) { for (const audio of queue.queuejson) {
audio.tce.innerText = audio.ts(); audio.tce.innerText = audio.ts();
break;
} }
} }
} }

View File

@ -1,19 +1,19 @@
import time
import contextlib
import asyncio import asyncio
import contextlib
import os import os
import sys import sys
import time
import traceback import traceback
import discord import discord
from v6d3music.app import MusicAppFactory, session_db 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.config import prefix
from v6d3music.core.cache_url import cache_db 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.mainasrc import main_for_raw_vc, mainasrcs
from v6d3music.core.mainaudio import volume_db from v6d3music.core.mainaudio import volume_db
from v6d3music.core.queueaudio import queue_db from v6d3music.core.queueaudio import queue_db
from v6d3music.core.entries_effects_for_args import effects_db
from rainbowadn.instrument import Instrumentation from rainbowadn.instrument import Instrumentation
from v6d1tokens.client import request_token from v6d1tokens.client import request_token
@ -24,7 +24,6 @@ from v6d2ctx.serve import serve
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
register_commands()
class MusicClient(discord.Client): class MusicClient(discord.Client):
@ -56,14 +55,7 @@ client = MusicClient(
vcs_restored = False vcs_restored = False
async def restore_vcs(): async def _restore_vc(guild: discord.Guild, vccid: int, vc_is_paused: bool) -> None:
global vcs_restored
vcs: list[tuple[int, int, bool]] = queue_db.get('vcs', [])
try:
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() channels = await guild.fetch_channels()
channel: discord.VoiceChannel channel: discord.VoiceChannel
channel, = [ channel, = [
@ -81,10 +73,29 @@ async def restore_vcs():
await main_for_raw_vc(vc, create=True, force_play=True) await main_for_raw_vc(vc, create=True, force_play=True)
if vc_is_paused: if vc_is_paused:
vc.pause() 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: except Exception as e:
print(f'vc {vcgid} {vccid} {vc_is_paused} failed', e) print(f'vc {vcgid} {vccid} {vc_is_paused} failed', e)
else: else:
print(f'vc restored {vcgid} {vccid}') 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:
tasks.append(asyncio.create_task(restore_vc(vcgid, vccid, vc_is_paused)))
for task in tasks:
await task
finally: finally:
vcs_restored = True vcs_restored = True
@ -116,7 +127,7 @@ def message_allowed(message: discord.Message) -> bool:
@client.event @client.event
async def on_message(message: discord.Message) -> None: async def on_message(message: discord.Message) -> None:
if message_allowed(message): 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): async def save_queues(delay: bool):

View File

@ -31,7 +31,7 @@ class UrlCtx:
try: try:
async for info in entries_for_url(self.url, self.tor): async for info in entries_for_url(self.url, self.tor):
yield InfoCtx(info, self.effects, self.already_read, self.tor, self.ignore) yield InfoCtx(info, self.effects, self.already_read, self.tor, self.ignore)
except: except Exception:
if not self.ignore: if not self.ignore:
raise raise

View File

@ -7,7 +7,7 @@ if (address := os.getenv('v6tor', None)) is not None:
try: try:
import socket import socket
address = socket.gethostbyname_ex(address)[2][0] address = socket.gethostbyname_ex(address)[2][0]
except: except Exception:
print('failed tor resolution') print('failed tor resolution')
_tor_prefix = None _tor_prefix = None
else: else: