diff options
| author | Tolmachev Igor <me@igorek.dev> | 2026-04-20 20:56:35 +0300 |
|---|---|---|
| committer | Tolmachev Igor <me@igorek.dev> | 2026-04-20 20:56:35 +0300 |
| commit | 75e99ca0712a2c09230e5c6f8d093dc526cc717d (patch) | |
| tree | f3f00494364a82b866f093651cb9a08030135c4e /libs | |
| parent | f186fca0f1aa9bbe5eab7613f229df527b2ab774 (diff) | |
| download | vpn_manager_bot-75e99ca0712a2c09230e5c6f8d093dc526cc717d.tar.gz vpn_manager_bot-75e99ca0712a2c09230e5c6f8d093dc526cc717d.zip | |
Add users command
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/__init__.py | 3 | ||||
| -rw-r--r-- | libs/storage.py | 75 | ||||
| -rw-r--r-- | libs/user.py | 30 |
3 files changed, 28 insertions, 80 deletions
diff --git a/libs/__init__.py b/libs/__init__.py index 55e6b19..48677db 100644 --- a/libs/__init__.py +++ b/libs/__init__.py | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | from . import fsm, invoice, msg, storage, user | 1 | from . import fsm, invoice, msg, user |
| 2 | 2 | ||
| 3 | __all__ = [ | 3 | __all__ = [ |
| 4 | "storage", | ||
| 5 | "fsm", | 4 | "fsm", |
| 6 | "msg", | 5 | "msg", |
| 7 | "user", | 6 | "user", |
diff --git a/libs/storage.py b/libs/storage.py deleted file mode 100644 index 6220cfc..0000000 --- a/libs/storage.py +++ /dev/null | |||
| @@ -1,75 +0,0 @@ | |||
| 1 | from pathlib import Path | ||
| 2 | from typing import Any, Mapping | ||
| 3 | |||
| 4 | from aiofiles import open as open | ||
| 5 | from aiogram.fsm.state import State | ||
| 6 | from aiogram.fsm.storage.base import ( | ||
| 7 | BaseStorage, | ||
| 8 | DefaultKeyBuilder, | ||
| 9 | KeyBuilder, | ||
| 10 | StateType, | ||
| 11 | StorageKey, | ||
| 12 | ) | ||
| 13 | from pydantic import TypeAdapter | ||
| 14 | from pydantic.main import BaseModel | ||
| 15 | |||
| 16 | |||
| 17 | class Record(BaseModel): | ||
| 18 | data: dict[str, Any] = {} | ||
| 19 | state: str | None = None | ||
| 20 | |||
| 21 | |||
| 22 | class JsonStorage(BaseStorage): | ||
| 23 | file_path: Path | ||
| 24 | records: dict[str, Record] | ||
| 25 | records_adapter: TypeAdapter | ||
| 26 | key_builder: KeyBuilder | ||
| 27 | |||
| 28 | def __init__(self, file_path: Path, key_builder: KeyBuilder | None = None) -> None: | ||
| 29 | self.file_path = file_path | ||
| 30 | self.records = {} | ||
| 31 | self.records_adapter = TypeAdapter(dict[str, Record]) | ||
| 32 | self.key_builder = DefaultKeyBuilder() if key_builder is None else key_builder | ||
| 33 | |||
| 34 | async def read(self) -> None: | ||
| 35 | async with open(self.file_path, "rb") as file: | ||
| 36 | json = await file.read() | ||
| 37 | self.records = self.records_adapter.validate_json(json) | ||
| 38 | |||
| 39 | async def flush(self) -> None: | ||
| 40 | async with open(self.file_path, "wb") as file: | ||
| 41 | json = self.records_adapter.dump_json(self.records) | ||
| 42 | await file.write(json) | ||
| 43 | |||
| 44 | async def get_record(self, key: StorageKey) -> Record: | ||
| 45 | await self.read() | ||
| 46 | record_key = self.key_builder.build(key) | ||
| 47 | if record_key not in self.records: | ||
| 48 | self.records[record_key] = Record() | ||
| 49 | return self.records[record_key] | ||
| 50 | |||
| 51 | async def set_state(self, key: StorageKey, state: StateType = None) -> None: | ||
| 52 | record = await self.get_record(key) | ||
| 53 | record.state = state.state if isinstance(state, State) else state | ||
| 54 | await self.flush() | ||
| 55 | |||
| 56 | async def get_state(self, key: StorageKey) -> str | None: | ||
| 57 | record = await self.get_record(key) | ||
| 58 | return record.state | ||
| 59 | |||
| 60 | async def set_data(self, key: StorageKey, data: Mapping[str, Any]) -> None: | ||
| 61 | if not isinstance(data, dict): | ||
| 62 | raise TypeError( | ||
| 63 | f"Data must be a dict or dict-like object, got {type(data).__name__}", | ||
| 64 | data, | ||
| 65 | ) | ||
| 66 | record = await self.get_record(key) | ||
| 67 | record.data = data.copy() | ||
| 68 | await self.flush() | ||
| 69 | |||
| 70 | async def get_data(self, key: StorageKey) -> dict[str, Any]: | ||
| 71 | record = await self.get_record(key) | ||
| 72 | return record.data | ||
| 73 | |||
| 74 | async def close(self) -> None: | ||
| 75 | await self.flush() | ||
diff --git a/libs/user.py b/libs/user.py index b201ce9..d145da6 100644 --- a/libs/user.py +++ b/libs/user.py | |||
| @@ -1,5 +1,29 @@ | |||
| 1 | from aiogram.types import Chat, User | 1 | from aiogram import Bot |
| 2 | 2 | ||
| 3 | from models import UserCache | ||
| 4 | from shared import redis_users | ||
| 3 | 5 | ||
| 4 | def mention(user: User | Chat) -> str: | 6 | |
| 5 | return f'<a href="tg://user?id={user.id}">{user.full_name}</a>' | 7 | async def set_user_cache(user_cache: UserCache) -> None: |
| 8 | await redis_users.set( | ||
| 9 | str(user_cache.id), | ||
| 10 | user_cache.model_dump_json( | ||
| 11 | exclude_defaults=True, | ||
| 12 | exclude_none=True, | ||
| 13 | ), | ||
| 14 | ) | ||
| 15 | |||
| 16 | |||
| 17 | async def get_user_cache(user_id: int) -> UserCache | None: | ||
| 18 | user_cache = await redis_users.get(str(user_id)) | ||
| 19 | if user_cache is None: | ||
| 20 | return None | ||
| 21 | return UserCache.model_validate_json(user_cache) | ||
| 22 | |||
| 23 | |||
| 24 | async def load_user_cache(bot: Bot, user_id: int) -> UserCache: | ||
| 25 | user_cache = await get_user_cache(user_id) | ||
| 26 | if user_cache is None: | ||
| 27 | user_cache = UserCache.from_chat(await bot.get_chat(user_id)) | ||
| 28 | await set_user_cache(user_cache) | ||
| 29 | return user_cache | ||
