diff --git a/v6d3music/app.py b/v6d3music/app.py
index 74f1322..553e0db 100644
--- a/v6d3music/app.py
+++ b/v6d3music/app.py
@@ -5,13 +5,16 @@ from pathlib import Path
 import aiohttp
 import discord
 from aiohttp import web
-from ptvp35 import Db
+from ptvp35 import Db, KVJson
 from v6d0auth.appfactory import AppFactory
 from v6d0auth.run_app import start_app
 from v6d1tokens.client import request_token
 
+from v6d3music.config import myroot
 from v6d3music.utils.bytes_hash import bytes_hash
 
+session_db = Db(myroot / 'session.db', kvrequest_type=KVJson)
+
 
 class MusicAppFactory(AppFactory):
     htmlroot = Path(__file__).parent / 'html'
@@ -19,7 +22,6 @@ class MusicAppFactory(AppFactory):
     def __init__(
             self,
             secret: str,
-            db: Db,
             client: discord.Client
     ):
         self.secret = secret
@@ -27,7 +29,6 @@ class MusicAppFactory(AppFactory):
         self.discord_auth = 'https://discord.com/api/oauth2/authorize?client_id=914432576926646322' \
                             f'&redirect_uri={urllib.parse.quote(self.redirect)}&response_type=code&scope=identify'
         self.loop = asyncio.get_running_loop()
-        self.db = db
         self.client = client
 
     def _file(self, file: str):
@@ -106,7 +107,7 @@ class MusicAppFactory(AppFactory):
         discriminator = cls.user_discriminator(user)
         if discriminator is None:
             return None
-        return username + discriminator
+        return f'{username}#{discriminator}'
 
     @classmethod
     def user_username(cls, user: dict):
@@ -147,8 +148,9 @@ class MusicAppFactory(AppFactory):
             'client': (None if sclient is None else self.client_status(sclient))
         }
 
-    def session_data(self, session: str) -> dict:
-        data = self.db.get(session, {})
+    @classmethod
+    def session_data(cls, session: str) -> dict:
+        data = session_db.get(session, {})
         if not isinstance(data, dict):
             data = {}
         return data
@@ -177,7 +179,7 @@ class MusicAppFactory(AppFactory):
                 data = self.session_data(session)
                 data['code'] = code
                 data['token'] = await self.code_token(code)
-                await self.db.set(session, data)
+                await session_db.set(session, data)
                 return response
             else:
                 return await self.html_resp('auth')
@@ -186,7 +188,7 @@ class MusicAppFactory(AppFactory):
         async def state(request: web.Request) -> web.Response:
             session = str(request.query.get('session'))
             return web.json_response(
-                data=f"{bytes_hash(session.encode())}"
+                data=f'{bytes_hash(session.encode())}'
             )
 
         @routes.get('/status/')
@@ -209,6 +211,6 @@ class MusicAppFactory(AppFactory):
             )
 
     @classmethod
-    async def start(cls, db: Db, client: discord.Client):
-        factory = cls(await request_token('music-client', 'token'), db, client)
+    async def start(cls, client: discord.Client):
+        factory = cls(await request_token('music-client', 'token'), client)
         await start_app(factory.app())
diff --git a/v6d3music/create_ytaudio.py b/v6d3music/create_ytaudio.py
new file mode 100644
index 0000000..9086769
--- /dev/null
+++ b/v6d3music/create_ytaudio.py
@@ -0,0 +1,32 @@
+import string
+from typing import Any, Optional
+
+from v6d2ctx.context import Context, Explicit, escape
+
+from v6d3music.real_url import real_url
+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(
+        ctx: Context, info: dict[str, Any], effects: Optional[str], already_read: int, tor: bool
+) -> YTAudio:
+    if effects:
+        if effects not in allowed_effects:
+            assert_admin(ctx.member)
+        if not set(effects) <= set(string.ascii_letters + string.digits + '*,=+-/()|.^:_'):
+            raise Explicit('malformed effects')
+        options = options_for_effects(effects)
+    else:
+        options = None
+    return YTAudio(
+        await real_url(info['url'], False, tor),
+        info['url'],
+        f'{escape(info.get("title", "unknown"))} `Rby` {ctx.member}',
+        options,
+        ctx.member,
+        already_read,
+        tor
+    )
diff --git a/v6d3music/create_ytaudios.py b/v6d3music/create_ytaudios.py
new file mode 100644
index 0000000..da52fb6
--- /dev/null
+++ b/v6d3music/create_ytaudios.py
@@ -0,0 +1,21 @@
+import asyncio
+from typing import AsyncIterable
+
+from v6d2ctx.context import Context
+
+from v6d3music.create_ytaudio import create_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]:
+    for audio in await asyncio.gather(
+            *[
+                create_ytaudio(ctx, info, effects, already_read, tor)
+                for
+                info, effects, already_read, tor
+                in
+                infos
+            ]
+    ):
+        yield audio
diff --git a/v6d3music/html/home.html b/v6d3music/html/home.html
index 64b048c..51ab999 100644
--- a/v6d3music/html/home.html
+++ b/v6d3music/html/home.html
@@ -3,11 +3,6 @@
 <script src="/main.js"></script>
 <script>
     (async () => {
-        const a = document.createElement('a');
-        a.href = '/login/';
-        a.innerText = 'login';
-        root.append(a);
-        logEl(JSON.stringify(await sessionStatus(), undefined, 2));
-        root.append(await userAvatarImg());
+        root.append(await pageHome());
     })();
 </script>
diff --git a/v6d3music/html/login.html b/v6d3music/html/login.html
index 877a2cd..c1f54fb 100644
--- a/v6d3music/html/login.html
+++ b/v6d3music/html/login.html
@@ -2,12 +2,12 @@
 <div id="root"></div>
 <script src="/main.js"></script>
 <script>
+    authbase = '$$DISCORD_AUTH$$';
     (async () => {
-        const a = document.createElement('a');
-        a.href = "$$DISCORD_AUTH$$&state=" + await sessionState();
-        a.innerText = 'auth';
+        const a = await aAuth();
         root.append(a);
         logEl(sessionStr());
         logEl(await sessionState());
+        a.click();
     })();
 </script>
diff --git a/v6d3music/html/main.css b/v6d3music/html/main.css
index 8411934..4b88557 100644
--- a/v6d3music/html/main.css
+++ b/v6d3music/html/main.css
@@ -1,4 +1,5 @@
 html, body {
     color: white;
     background: black;
+    margin: 0;
 }
diff --git a/v6d3music/html/main.js b/v6d3music/html/main.js
index eb2c44a..6e1c013 100644
--- a/v6d3music/html/main.js
+++ b/v6d3music/html/main.js
@@ -49,10 +49,45 @@ const userUsername = async () => {
     return user && user['username'];
 };
 const userAvatarImg = async () => {
-    const img = document.createElement('img');
-    img.src = await userAvatarUrl();
-    img.width = 128;
-    img.height = 128;
-    img.alt = await userUsername();
-    return img;
+    const avatar = await userAvatarUrl();
+    if (avatar) {
+        const img = document.createElement('img');
+        img.src = avatar;
+        img.width = 128;
+        img.height = 128;
+        img.alt = await userUsername();
+        return img;
+    } else {
+        return baseEl('span');
+    }
+};
+const userId = async () => {
+    const user = await sessionUser();
+    return user && user['id'];
+};
+const baseEl = (tag, ...appended) => {
+    const element = document.createElement(tag);
+    element.append(...appended);
+    return element;
+};
+const aLogin = () => {
+    const a = document.createElement('a');
+    a.href = '/login/';
+    a.innerText = 'login';
+    return a;
+};
+const pageHome = async () => {
+    return baseEl(
+        'div',
+        baseEl('div', aLogin()),
+        baseEl('div', await userAvatarImg()),
+        await userId()
+    )
+};
+let authbase;
+const aAuth = async () => {
+    const a = document.createElement('a');
+    a.href = authbase + '&state=' + await sessionState();
+    a.innerText = 'auth';
+    return a;
 };
diff --git a/v6d3music/mainaudio.py b/v6d3music/mainaudio.py
new file mode 100644
index 0000000..b9fa4a6
--- /dev/null
+++ b/v6d3music/mainaudio.py
@@ -0,0 +1,28 @@
+import discord
+from ptvp35 import Db, KVJson
+from v6d2ctx.context import Explicit
+
+from v6d3music.config import myroot
+from v6d3music.queueaudio import QueueAudio
+from v6d3music.utils.assert_admin import assert_admin
+
+volume_db = Db(myroot / 'volume.db', kvrequest_type=KVJson)
+
+
+class MainAudio(discord.PCMVolumeTransformer):
+    def __init__(self, queue: QueueAudio, volume: float):
+        self.queue = queue
+        super().__init__(self.queue, volume=volume)
+
+    async def set(self, volume: float, member: discord.Member):
+        assert_admin(member)
+        if volume < 0.01:
+            raise Explicit('volume too small')
+        if volume > 1:
+            raise Explicit('volume too big')
+        self.volume = volume
+        await volume_db.set(member.guild.id, volume)
+
+    @classmethod
+    async def create(cls, guild: discord.Guild) -> 'MainAudio':
+        return cls(await QueueAudio.create(guild), volume=volume_db.get(guild.id, 0.2))
diff --git a/v6d3music/queueaudio.py b/v6d3music/queueaudio.py
new file mode 100644
index 0000000..f68af30
--- /dev/null
+++ b/v6d3music/queueaudio.py
@@ -0,0 +1,107 @@
+import asyncio
+from collections import deque
+from io import StringIO
+
+import discord
+from ptvp35 import Db, KVJson
+
+from v6d3music.config import myroot
+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)
+
+
+class QueueAudio(discord.AudioSource):
+    def __init__(self, guild: discord.Guild, respawned: list[YTAudio]):
+        self.queue: deque[YTAudio] = deque()
+        self.queue.extend(respawned)
+        self.guild = guild
+
+    @staticmethod
+    async def respawned(guild: discord.Guild) -> list[YTAudio]:
+        respawned = []
+        try:
+            for audio_respawn in queue_db.get(guild.id, []):
+                try:
+                    respawned.append(await YTAudio.respawn(guild, audio_respawn))
+                except Exception as e:
+                    print('audio respawn failed', e)
+                    raise
+        except Exception as e:
+            print('queue respawn failed', e)
+        return respawned
+
+    @classmethod
+    async def create(cls, guild: discord.Guild):
+        return cls(guild, await QueueAudio.respawned(guild))
+
+    async def save(self):
+        hybernated = []
+        for audio in list(self.queue):
+            await asyncio.sleep(0.01)
+            hybernated.append(audio.hybernate())
+        queue_db.set_nowait(self.guild.id, hybernated)
+
+    def append(self, audio: YTAudio):
+        self.queue.append(audio)
+
+    def read(self) -> bytes:
+        if not self.queue:
+            return FILL
+        audio = self.queue[0]
+        frame = audio.read()
+        if len(frame) != discord.opus.Encoder.FRAME_SIZE:
+            self.queue.popleft().cleanup()
+            frame = FILL
+        return frame
+
+    def skip_at(self, pos: int, member: discord.Member) -> bool:
+        if pos < len(self.queue):
+            audio = self.queue[pos]
+            if audio.can_be_skipped_by(member):
+                self.queue.remove(audio)
+                audio.cleanup()
+                return True
+        return False
+
+    def skip_audio(self, audio: YTAudio, member: discord.Member) -> bool:
+        if audio in self.queue:
+            if audio.can_be_skipped_by(member):
+                self.queue.remove(audio)
+                audio.cleanup()
+                return True
+        return False
+
+    def clear(self, member: discord.Member) -> None:
+        assert_admin(member)
+        self.cleanup()
+
+    def swap(self, member: discord.Member, a: int, b: int) -> None:
+        assert_admin(member)
+        if max(a, b) >= len(self.queue):
+            return
+        self.queue[a], self.queue[b] = self.queue[b], self.queue[a]
+
+    def move(self, member: discord.Member, a: int, b: int) -> None:
+        assert_admin(member)
+        if max(a, b) >= len(self.queue):
+            return
+        audio = self.queue[a]
+        self.queue.remove(audio)
+        self.queue.insert(b, audio)
+
+    async def format(self) -> str:
+        stream = StringIO()
+        for i, audio in enumerate(list(self.queue)):
+            stream.write(f'`[{i}]` `{audio.source_timecode()} / {audio.duration()}` {audio.description}\n')
+        return stream.getvalue()
+
+    def cleanup(self):
+        self.queue.clear()
+        for audio in self.queue:
+            try:
+                audio.cleanup()
+            except ValueError:
+                pass
diff --git a/v6d3music/run-bot.py b/v6d3music/run-bot.py
index 3e8e893..9680f23 100644
--- a/v6d3music/run-bot.py
+++ b/v6d3music/run-bot.py
@@ -1,34 +1,27 @@
 import asyncio
-import concurrent.futures
-import json
 import os
 import shlex
-import string
 import subprocess
 import time
-from collections import deque
-from io import StringIO
-from typing import Any, AsyncIterable, Iterable, Optional, TypeAlias
 
 import discord
-from ptvp35 import Db, KVJson
 from v6d1tokens.client import request_token
-from v6d2ctx.context import Benchmark, Context, Explicit, Implicit, at, escape, monitor
+from v6d2ctx.context import Benchmark, Context, Explicit, at, monitor
 from v6d2ctx.handle_content import handle_content
 from v6d2ctx.lock_for import lock_for
 from v6d2ctx.serve import serve
 
-import v6d3music.extract
-import v6d3music.ffmpegnormalaudio
-from v6d3music.app import MusicAppFactory
+from v6d3music.app import MusicAppFactory, session_db
 from v6d3music.cache_url import cache_db
-from v6d3music.config import myroot, prefix
-from v6d3music.real_url import real_url
+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.fill import FILL
+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.sparq import sparq
-from v6d3music.ytaudio import YTAudio
+from v6d3music.utils.presets import allowed_presets
+from v6d3music.yt_audios import yt_audios
 
 loop = asyncio.new_event_loop()
 asyncio.set_event_loop(loop)
@@ -45,9 +38,6 @@ client = discord.Client(
         reactions=True
     ),
 )
-volume_db = Db(myroot / 'volume.db', kvrequest_type=KVJson)
-queue_db = Db(myroot / 'queue.db', kvrequest_type=KVJson)
-session_db = Db(myroot / 'session.db', kvrequest_type=KVJson)
 
 vcs_restored = False
 
@@ -85,8 +75,9 @@ async def on_ready():
             name='феноменально',
         )
     )
-    if not vcs_restored:
-        await restore_vcs()
+    async with lock_for('vcs_restored', '...'):
+        if not vcs_restored:
+            await restore_vcs()
 
 
 @at('commands', 'help')
@@ -98,283 +89,9 @@ async def help_(ctx: Context, args: list[str]) -> None:
             await ctx.reply(f'help for {name}: `{name} help`')
 
 
-class QueueAudio(discord.AudioSource):
-    def __init__(self, guild: discord.Guild, respawned: list[YTAudio]):
-        self.queue: deque[YTAudio] = deque()
-        self.queue.extend(respawned)
-        self.guild = guild
-
-    @staticmethod
-    async def respawned(guild: discord.Guild) -> list[YTAudio]:
-        respawned = []
-        try:
-            for audio_respawn in queue_db.get(guild.id, []):
-                try:
-                    respawned.append(await YTAudio.respawn(guild, audio_respawn))
-                except Exception as e:
-                    print('audio respawn failed', e)
-                    raise
-        except Exception as e:
-            print('queue respawn failed', e)
-        return respawned
-
-    @classmethod
-    async def create(cls, guild: discord.Guild):
-        return cls(guild, await QueueAudio.respawned(guild))
-
-    async def save(self):
-        hybernated = []
-        for audio in list(self.queue):
-            await asyncio.sleep(0.01)
-            hybernated.append(audio.hybernate())
-        queue_db.set_nowait(self.guild.id, hybernated)
-
-    def append(self, audio: YTAudio):
-        self.queue.append(audio)
-
-    def read(self) -> bytes:
-        if not self.queue:
-            return FILL
-        audio = self.queue[0]
-        frame = audio.read()
-        if len(frame) != discord.opus.Encoder.FRAME_SIZE:
-            self.queue.popleft().cleanup()
-            frame = FILL
-        return frame
-
-    def skip_at(self, pos: int, member: discord.Member) -> bool:
-        if pos < len(self.queue):
-            audio = self.queue[pos]
-            if audio.can_be_skipped_by(member):
-                self.queue.remove(audio)
-                audio.cleanup()
-                return True
-        return False
-
-    def skip_audio(self, audio: YTAudio, member: discord.Member) -> bool:
-        if audio in self.queue:
-            if audio.can_be_skipped_by(member):
-                self.queue.remove(audio)
-                audio.cleanup()
-                return True
-        return False
-
-    def clear(self, member: discord.Member) -> None:
-        assert_admin(member)
-        self.cleanup()
-
-    def swap(self, member: discord.Member, a: int, b: int) -> None:
-        assert_admin(member)
-        if max(a, b) >= len(self.queue):
-            return
-        self.queue[a], self.queue[b] = self.queue[b], self.queue[a]
-
-    def move(self, member: discord.Member, a: int, b: int) -> None:
-        assert_admin(member)
-        if max(a, b) >= len(self.queue):
-            return
-        audio = self.queue[a]
-        self.queue.remove(audio)
-        self.queue.insert(b, audio)
-
-    async def format(self) -> str:
-        stream = StringIO()
-        for i, audio in enumerate(list(self.queue)):
-            stream.write(f'`[{i}]` `{audio.source_timecode()} / {audio.duration()}` {audio.description}\n')
-        return stream.getvalue()
-
-    def cleanup(self):
-        self.queue.clear()
-        for audio in self.queue:
-            try:
-                audio.cleanup()
-            except ValueError:
-                pass
-
-
-class MainAudio(discord.PCMVolumeTransformer):
-    def __init__(self, queue: QueueAudio, volume: float):
-        self.queue = queue
-        super().__init__(self.queue, volume=volume)
-
-    async def set(self, volume: float, member: discord.Member):
-        assert_admin(member)
-        if volume < 0.01:
-            raise Explicit('volume too small')
-        if volume > 1:
-            raise Explicit('volume too big')
-        self.volume = volume
-        await volume_db.set(member.guild.id, volume)
-
-
-async def aextract(params: dict, url: str, **kwargs):
-    with Benchmark('AEX'):
-        with concurrent.futures.ProcessPoolExecutor() as pool:
-            return await loop.run_in_executor(
-                pool,
-                v6d3music.extract.extract,
-                params,
-                url,
-                kwargs
-            )
-
-
-async def tor_extract(params: dict, url: str, **kwargs):
-    print(f'tor extracting {url}')
-    p = subprocess.Popen(
-        ['torify', 'python', '-m', 'v6d3music.run-extract'],
-        stdin=subprocess.PIPE,
-        stdout=subprocess.PIPE,
-        text=True
-    )
-    p.stdin.write(f'{json.dumps(params)}\n')
-    p.stdin.write(f'{json.dumps(url)}\n')
-    p.stdin.write(f'{json.dumps(kwargs)}\n')
-    p.stdin.flush()
-    p.stdin.close()
-    code = await loop.run_in_executor(None, p.wait)
-    if code:
-        raise RuntimeError(code)
-    return json.loads(p.stdout.read())
-
-
-async def create_ytaudio(
-        ctx: Context, info: dict[str, Any], effects: Optional[str], already_read: int, tor: bool
-) -> YTAudio:
-    if effects:
-        if effects not in allowed_effects:
-            assert_admin(ctx.member)
-        if not set(effects) <= set(string.ascii_letters + string.digits + '*,=+-/()|.^:_'):
-            raise Explicit('malformed effects')
-        options = options_for_effects(effects)
-    else:
-        options = None
-    return YTAudio(
-        await real_url(info['url'], False, tor),
-        info['url'],
-        f'{escape(info.get("title", "unknown"))} `Rby` {ctx.member}',
-        options,
-        ctx.member,
-        already_read,
-        tor
-    )
-
-
-async def entries_for_url(url: str, tor: bool) -> AsyncIterable[
-    dict[str, Any]
-]:
-    ef = aextract
-    if tor:
-        ef = tor_extract
-    info = await ef(
-        {
-            'playlistend': 128,
-            'logtostderr': True
-        },
-        url,
-        download=False,
-        process=False
-    )
-    if '__error__' in info:
-        raise Explicit('extraction error\n' + info.get('__error_str__'))
-    if 'entries' in info:
-        for entry in info['entries']:
-            yield entry
-    else:
-        yield info | {'url': url}
-
-
-info_tuple: TypeAlias = tuple[dict[str, Any], str, int, bool]
-
-
-async def create_ytaudios(ctx: Context, infos: list[info_tuple]) -> AsyncIterable[YTAudio]:
-    for audio in await asyncio.gather(
-            *[
-                create_ytaudio(ctx, info, effects, already_read, tor)
-                for
-                info, effects, already_read, tor
-                in
-                infos
-            ]
-    ):
-        yield audio
-
-
-presets: dict[str, str] = {
-    'cursed': 'aeval=val(0)*2*sin(440*t)+val(1)*2*cos(622*t)|val(1)*2*sin(622*t)+val(0)*2*cos(440*t)',
-    'bassboost': 'bass=g=10',
-    'bassbooboost': 'bass=g=30',
-    'nightcore': 'asetrate=67882',
-    'daycore': 'atempo=.9,aecho=1.0:0.5:20:0.5',
-    'пришествие анимешне': 'bass=g=15,asetrate=67882,bass=g=15',
-    'difference': 'aeval=val(0)-val(1)|val(1)-val(0)',
-    'mono': 'aeval=.5*val(0)+.5*val(1)|.5*val(1)+.5*val(0)',
-}
-allowed_presets = ['bassboost', 'bassbooboost', 'nightcore', 'daycore', 'mono']
-allowed_effects = {'', *(presets[key] for key in allowed_presets)}
-
-
-def effects_for_preset(preset: str) -> str:
-    if preset in presets:
-        return presets[preset]
-    else:
-        raise Explicit('unknown preset')
-
-
-async def entries_effects_for_args(args: list[str]) -> AsyncIterable[info_tuple]:
-    while args:
-        match args:
-            case [url, '-', effects, *args]:
-                pass
-            case [url, '+', preset, *args]:
-                effects = effects_for_preset(preset)
-            case [url, *args]:
-                effects = None
-            case _:
-                raise RuntimeError
-        seconds = 0
-        match args:
-            case [h, m, s, *args] if h.isdecimal() and m.isdecimal() and s.isdecimal():
-                seconds = 3600 * int(h) + 60 * int(m) + int(s)
-            case [m, s, *args] if m.isdecimal() and s.isdecimal():
-                seconds = 60 * int(m) + int(s)
-            case [s, *args] if s.isdecimal():
-                seconds = int(s)
-            case [*args]:
-                pass
-        already_read = round(seconds / sparq(options_for_effects(effects)))
-        tor = False
-        match args:
-            case ['tor', *args]:
-                tor = True
-            case [*args]:
-                pass
-        async for info in entries_for_url(url, tor):
-            yield info, effects, already_read, tor
-
-
-async def yt_audios(ctx: Context, args: list[str]) -> AsyncIterable[YTAudio]:
-    tuples: list[info_tuple] = []
-    async for info, effects, already_read, tor in entries_effects_for_args(args):
-        tuples.append((info, effects, already_read, tor))
-        if len(tuples) >= 5:
-            async for audio in create_ytaudios(ctx, tuples):
-                yield audio
-            tuples.clear()
-    async for audio in create_ytaudios(ctx, tuples):
-        yield audio
-
-
 mainasrcs: dict[discord.Guild, MainAudio] = {}
 
 
-async def catch(ctx: Context, args: list[str], reply: str, *catched: (Iterable[str] | str)):
-    catched = {(case,) if isinstance(case, str) else tuple(case) for case in catched}
-    if tuple(args) in catched:
-        await ctx.reply(reply.strip())
-        raise Implicit
-
-
 @at('commands', '/')
 @at('commands', 'play')
 async def play(ctx: Context, args: list[str]) -> None:
@@ -422,7 +139,7 @@ async def main_for_raw_vc(vc: discord.VoiceClient, *, create: bool) -> MainAudio
         if create:
             source = mainasrcs.setdefault(
                 vc.guild,
-                MainAudio(await QueueAudio.create(vc.guild), volume=volume_db.get(vc.guild.id, 0.2))
+                await MainAudio.create(vc.guild)
             )
         else:
             raise Explicit('not playing')
@@ -644,7 +361,7 @@ async def save_job():
 
 
 async def start_app():
-    await MusicAppFactory.start(session_db, client)
+    await MusicAppFactory.start(client)
 
 
 async def setup_tasks():
diff --git a/v6d3music/run-extract.py b/v6d3music/run-extract.py
index f67bff3..ec925a4 100644
--- a/v6d3music/run-extract.py
+++ b/v6d3music/run-extract.py
@@ -1,8 +1,8 @@
 import json
 
-import v6d3music.extract
+import v6d3music.utils.extract
 
 params = json.loads(input())
 url = json.loads(input())
 kwargs = json.loads(input())
-print(json.dumps(v6d3music.extract.extract(params, url, kwargs)))
+print(json.dumps(v6d3music.utils.extract.extract(params, url, kwargs)))
diff --git a/v6d3music/utils/aextract.py b/v6d3music/utils/aextract.py
new file mode 100644
index 0000000..a6fa580
--- /dev/null
+++ b/v6d3music/utils/aextract.py
@@ -0,0 +1,18 @@
+import asyncio
+from concurrent.futures import ProcessPoolExecutor
+
+from v6d2ctx.context import Benchmark
+
+from v6d3music.utils.extract import extract
+
+
+async def aextract(params: dict, url: str, **kwargs):
+    with Benchmark('AEX'):
+        with ProcessPoolExecutor() as pool:
+            return await asyncio.get_running_loop().run_in_executor(
+                pool,
+                extract,
+                params,
+                url,
+                kwargs
+            )
diff --git a/v6d3music/utils/catch.py b/v6d3music/utils/catch.py
new file mode 100644
index 0000000..7589047
--- /dev/null
+++ b/v6d3music/utils/catch.py
@@ -0,0 +1,10 @@
+from typing import Iterable
+
+from v6d2ctx.context import Context, Implicit
+
+
+async def catch(ctx: Context, args: list[str], reply: str, *catched: (Iterable[str] | str)):
+    catched = {(case,) if isinstance(case, str) else tuple(case) for case in catched}
+    if tuple(args) in catched:
+        await ctx.reply(reply.strip())
+        raise Implicit
diff --git a/v6d3music/utils/effects_for_preset.py b/v6d3music/utils/effects_for_preset.py
new file mode 100644
index 0000000..9eaea8f
--- /dev/null
+++ b/v6d3music/utils/effects_for_preset.py
@@ -0,0 +1,10 @@
+from v6d2ctx.context import Explicit
+
+from v6d3music.utils.presets import presets
+
+
+def effects_for_preset(preset: str) -> str:
+    if preset in presets:
+        return presets[preset]
+    else:
+        raise Explicit('unknown preset')
diff --git a/v6d3music/utils/entries_effects_for_args.py b/v6d3music/utils/entries_effects_for_args.py
new file mode 100644
index 0000000..f943ed1
--- /dev/null
+++ b/v6d3music/utils/entries_effects_for_args.py
@@ -0,0 +1,39 @@
+from typing import AsyncIterable
+
+from v6d3music.utils.effects_for_preset import effects_for_preset
+from v6d3music.utils.entries_for_url import entries_for_url
+from v6d3music.utils.info_tuple import info_tuple
+from v6d3music.utils.options_for_effects import options_for_effects
+from v6d3music.utils.sparq import sparq
+
+
+async def entries_effects_for_args(args: list[str]) -> AsyncIterable[info_tuple]:
+    while args:
+        match args:
+            case [url, '-', effects, *args]:
+                pass
+            case [url, '+', preset, *args]:
+                effects = effects_for_preset(preset)
+            case [url, *args]:
+                effects = None
+            case _:
+                raise RuntimeError
+        seconds = 0
+        match args:
+            case [h, m, s, *args] if h.isdecimal() and m.isdecimal() and s.isdecimal():
+                seconds = 3600 * int(h) + 60 * int(m) + int(s)
+            case [m, s, *args] if m.isdecimal() and s.isdecimal():
+                seconds = 60 * int(m) + int(s)
+            case [s, *args] if s.isdecimal():
+                seconds = int(s)
+            case [*args]:
+                pass
+        already_read = round(seconds / sparq(options_for_effects(effects)))
+        tor = False
+        match args:
+            case ['tor', *args]:
+                tor = True
+            case [*args]:
+                pass
+        async for info in entries_for_url(url, tor):
+            yield info, effects, already_read, tor
diff --git a/v6d3music/utils/entries_for_url.py b/v6d3music/utils/entries_for_url.py
new file mode 100644
index 0000000..10770a1
--- /dev/null
+++ b/v6d3music/utils/entries_for_url.py
@@ -0,0 +1,30 @@
+from typing import Any, AsyncIterable
+
+from v6d2ctx.context import Explicit
+
+from v6d3music.utils.aextract import aextract
+from v6d3music.utils.tor_extract import tor_extract
+
+
+async def entries_for_url(url: str, tor: bool) -> AsyncIterable[
+    dict[str, Any]
+]:
+    ef = aextract
+    if tor:
+        ef = tor_extract
+    info = await ef(
+        {
+            'playlistend': 128,
+            'logtostderr': True
+        },
+        url,
+        download=False,
+        process=False
+    )
+    if '__error__' in info:
+        raise Explicit('extraction error\n' + info.get('__error_str__'))
+    if 'entries' in info:
+        for entry in info['entries']:
+            yield entry
+    else:
+        yield info | {'url': url}
diff --git a/v6d3music/extract.py b/v6d3music/utils/extract.py
similarity index 89%
rename from v6d3music/extract.py
rename to v6d3music/utils/extract.py
index 0c0a4ef..c76ab08 100644
--- a/v6d3music/extract.py
+++ b/v6d3music/utils/extract.py
@@ -1,10 +1,6 @@
-from collections import namedtuple
-
 import discord.utils
 import youtube_dl
 
-eerror = namedtuple('eerror', ['content'])
-
 
 def extract(params: dict, url: str, kwargs: dict):
     try:
diff --git a/v6d3music/utils/info_tuple.py b/v6d3music/utils/info_tuple.py
new file mode 100644
index 0000000..07db908
--- /dev/null
+++ b/v6d3music/utils/info_tuple.py
@@ -0,0 +1,3 @@
+from typing import Any, TypeAlias
+
+info_tuple: TypeAlias = tuple[dict[str, Any], str, int, bool]
diff --git a/v6d3music/utils/presets.py b/v6d3music/utils/presets.py
new file mode 100644
index 0000000..711c304
--- /dev/null
+++ b/v6d3music/utils/presets.py
@@ -0,0 +1,12 @@
+presets: dict[str, str] = {
+    'cursed': 'aeval=val(0)*2*sin(440*t)+val(1)*2*cos(622*t)|val(1)*2*sin(622*t)+val(0)*2*cos(440*t)',
+    'bassboost': 'bass=g=10',
+    'bassbooboost': 'bass=g=30',
+    'nightcore': 'asetrate=67882',
+    'daycore': 'atempo=.9,aecho=1.0:0.5:20:0.5',
+    'пришествие анимешне': 'bass=g=15,asetrate=67882,bass=g=15',
+    'difference': 'aeval=val(0)-val(1)|val(1)-val(0)',
+    'mono': 'aeval=.5*val(0)+.5*val(1)|.5*val(1)+.5*val(0)',
+}
+allowed_presets = ['bassboost', 'bassbooboost', 'nightcore', 'daycore', 'mono']
+allowed_effects = {'', *(presets[key] for key in allowed_presets)}
diff --git a/v6d3music/utils/tor_extract.py b/v6d3music/utils/tor_extract.py
new file mode 100644
index 0000000..7071057
--- /dev/null
+++ b/v6d3music/utils/tor_extract.py
@@ -0,0 +1,22 @@
+import asyncio
+import json
+import subprocess
+
+
+async def tor_extract(params: dict, url: str, **kwargs):
+    print(f'tor extracting {url}')
+    p = subprocess.Popen(
+        ['torify', 'python', '-m', 'v6d3music.run-extract'],
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        text=True
+    )
+    p.stdin.write(f'{json.dumps(params)}\n')
+    p.stdin.write(f'{json.dumps(url)}\n')
+    p.stdin.write(f'{json.dumps(kwargs)}\n')
+    p.stdin.flush()
+    p.stdin.close()
+    code = await asyncio.get_running_loop().run_in_executor(None, p.wait)
+    if code:
+        raise RuntimeError(code)
+    return json.loads(p.stdout.read())
diff --git a/v6d3music/yt_audios.py b/v6d3music/yt_audios.py
new file mode 100644
index 0000000..0b481bf
--- /dev/null
+++ b/v6d3music/yt_audios.py
@@ -0,0 +1,20 @@
+from typing import AsyncIterable
+
+from v6d2ctx.context import Context
+
+from v6d3music.create_ytaudios import create_ytaudios
+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]:
+    tuples: list[info_tuple] = []
+    async for info, effects, already_read, tor in entries_effects_for_args(args):
+        tuples.append((info, effects, already_read, tor))
+        if len(tuples) >= 5:
+            async for audio in create_ytaudios(ctx, tuples):
+                yield audio
+            tuples.clear()
+    async for audio in create_ytaudios(ctx, tuples):
+        yield audio