Project structure:

DarkBot/
├── darkData.py
├── darkbot.py
├── db.py
└── filters.py


Project files:

darkbot.py
```py
import asyncio
import logging
import sys
from typing import Union, List

import aiogram
from aiogram import Bot, Dispatcher, types
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.filters import Command, BaseFilter
from aiogram.types import Message

from DarkBot.darkData import DarkData
from DarkBot.filters import DarkCommand, DarkFilter, WaitingForFilter


# darkbot.py

class DarkBot:
    def __init__(self, token: str):
        self.bot = Bot(
            token=token,
            default=DefaultBotProperties(parse_mode=ParseMode.HTML)
        )
        self.aiogram_dispatcher = Dispatcher()

    # Ваши собственные декораторы
    def message(self, filter: Union[DarkFilter, BaseFilter]):
        def decorator(func):
            if isinstance(filter, DarkFilter):
                aiogram_filter = filter.get_inst()
            elif isinstance(filter, BaseFilter):
                aiogram_filter = filter
            else:
                raise TypeError("Invalid filter type. Must be DarkFilter or aiogram.filters.BaseFilter.")

            @self.aiogram_dispatcher.message(aiogram_filter)
            async def wrapper(message: types.Message):
                return await func(DarkData(message))

            return wrapper

        return decorator

    def waiting_for(self, state_description: str, expected_types: List[str]):
        def decorator(func):
            filter = WaitingForFilter(state_description, expected_types)
            @self.aiogram_dispatcher.message(filter)
            async def wrapper(message: types.Message):
                dark_data = DarkData(message)
                # Очистка ожидаемого состояния после получения сообщения
                await dark_data.user.clear_waiting_for()
                return await func(dark_data)
            return wrapper
        return decorator

    def start(self, is_logging=False):
        """
        starts the bot with asyncio

        :param logging:
        :return:
        """
        async def main() -> None:
            await self.aiogram_dispatcher.start_polling(self.bot)

        if is_logging:
            logging.basicConfig(level=logging.INFO, stream=sys.stdout)

        asyncio.run(main())


```


darkData.py
```py
# darkData.py
from typing import TYPE_CHECKING
from aiogram.types import Message
from DarkBot.db import DarkDB

if TYPE_CHECKING:
    from aiogram import Bot

class DarkUser:
    def __init__(self, aiogram_user):
        self.id = aiogram_user.id
        self.data = {}
        self.db = DarkDB()
        self._initialized = False

    async def init(self):
        if not self._initialized:
            self.data = await self.db.get_user_data(self.id)
            self._initialized = True

    async def set_val(self, key, value):
        await self.init()
        self.data[key] = value
        await self.db.set_user_data(self.id, self.data)

    async def get_val(self, key, default=None):
        await self.init()
        print(f"self.data = {self.data}")
        return self.data.get(key, default)

    async def set_state(self, state: str):
        await self.set_val('_state', state)

    async def get_state(self):
        return await self.get_val('_state', None)

    # Метод для установки ожидаемого состояния
    async def set_waiting_for(self, state_description: str, expected_type: str):
        await self.set_val('_waiting_for', {'state': state_description, 'type': expected_type})

    # Метод для получения ожидаемого состояния
    async def get_waiting_for(self):
        return await self.get_val('_waiting_for', None)

    # Метод для очистки ожидаемого состояния
    async def clear_waiting_for(self):
        await self.set_val('_waiting_for', None)

class DarkData:
    def __init__(self, message: Message):
        self.message: Message = message
        self.user = DarkUser(message.from_user)
```


db.py
```py
# db.py
import aiosqlite
import json
import asyncio

class DarkDB:
    def __init__(self, db_path="darkbot.db"):
        self.db_path = db_path
        self._init_lock = asyncio.Lock()
        self._initialized = False

    async def init_db(self):
        async with self._init_lock:
            if not self._initialized:
                async with aiosqlite.connect(self.db_path) as db:
                    await db.execute('''
                        CREATE TABLE IF NOT EXISTS users (
                            id INTEGER PRIMARY KEY,
                            darkdata TEXT
                        )
                    ''')
                    await db.commit()
                self._initialized = True

    async def get_user_data(self, user_id: int) -> dict:
        await self.init_db()
        async with aiosqlite.connect(self.db_path) as db:
            async with db.execute('SELECT darkdata FROM users WHERE id = ?', (user_id,)) as cursor:
                row = await cursor.fetchone()
                if row:
                    return json.loads(row[0])
                else:
                    return {}

    async def set_user_data(self, user_id: int, data: dict):
        await self.init_db()
        darkdata_json = json.dumps(data)
        async with aiosqlite.connect(self.db_path) as db:
            await db.execute('''
                INSERT INTO users (id, darkdata) VALUES (?, ?)
                ON CONFLICT(id) DO UPDATE SET darkdata = excluded.darkdata
            ''', (user_id, darkdata_json))
            await db.commit()
```


filters.py
```py
from typing import Optional, List

from aiogram.filters import BaseFilter, Command
from aiogram.types import Message

from DarkBot.darkData import DarkUser


class DarkFilter:
    def get_inst(self) -> BaseFilter:
        raise NotImplementedError("You must implement get_inst() for your specific logic")


class DarkCommand(DarkFilter):
    def __init__(
            self,
            commands: List[str],  # Используем List[str] для type hinting
            prefix: str = "/"

    ):
        self.commands = commands
        self.prefix = prefix

    def get_inst(self) -> Command: # Указываем возвращаемый тип Command
        return Command(commands=self.commands, prefix=self.prefix)

class WaitingForFilter(BaseFilter):
    def __init__(self, state_description: str, expected_types: List[str]):
        self.state_description = state_description
        self.expected_types = expected_types

    async def __call__(self, message: Message) -> bool:
        user = DarkUser(message.from_user)
        waiting_for = await user.get_waiting_for()
        if waiting_for:
            if waiting_for['state'] == self.state_description:
                # Проверяем тип сообщения
                for expected_type in self.expected_types:
                    if expected_type == 'text' and message.text:
                        return True
                    elif expected_type == 'voice' and message.voice:
                        return True
                    elif expected_type == 'document' and message.document:
                        return True
                    elif expected_type == 'video' and message.video:
                        return True
                    # Добавьте проверки для других типов по необходимости
        return False
```


Привет, это мой фреймворк для простого создания телеграмм-ботов на основе aiogram.
Можешь пожалуйста сделать TODO в файле main.py
Особенно важен пункт `Сделать так, чтобы класс кнопки принимал в себя обязательный аргумент - text, а также на выбор, либо стандартный callback_data из aiogram(просто текст), либо напрямую можно было бы подать функцию, которая должна запуститься`. Я не знаю как его точно можно реализовать, но я хочу сделать так, что если мы передаем локальную функцию(внутри существующей, обработчика сообщения), то нужно как-то сделать так, что если она вызовется, то она спокойно будет иметь доступ ко всем локальным переменным, сделанном выше(в том же самом обработчика).
Например:
```py
@DarkBot.message(filters.DarkCommand(["name"]))
async def start(data: darkData) -> None:
    a = 2
    # Тут создать клавиатуру
    # Тут написать сообщение с переданной клавиатурой
    def on_press_ok_button():
        print(a) # выведет 2
```

Можешь пожалуйста это МАКСИМАЛЬНО грамотно реализовать.
Представь что ты senior программист и работаешь над проектом.

Спасибо!