bootstrap v2 stable start

This commit is contained in:
2026-04-13 20:47:04 +03:00
commit 7565aa485e
51 changed files with 1190 additions and 0 deletions

5
app/.env.example Normal file
View File

@@ -0,0 +1,5 @@
BOT_TOKEN=PUT_YOUR_TELEGRAM_BOT_TOKEN_HERE
BOT_PARSE_MODE=HTML
APP_ENV=dev
LOG_LEVEL=INFO
TZ=Europe/Madrid

3
app/README.md Normal file
View File

@@ -0,0 +1,3 @@
# app
Здесь находятся исходный код приложения, env-файлы и зависимости.

2
app/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
aiogram==3.13.1
python-dotenv==1.0.1

1
app/src/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1,22 @@
from __future__ import annotations
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from src.bootstrap.logging import setup_logging
from src.core.config import load_settings
from src.telegram.routers import setup_routers
def create_app() -> tuple[Bot, Dispatcher]:
settings = load_settings()
setup_logging(settings.log_level)
bot = Bot(
token=settings.bot_token,
default=DefaultBotProperties(parse_mode=settings.bot_parse_mode),
)
dispatcher = Dispatcher()
setup_routers(dispatcher)
return bot, dispatcher

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
import logging
def setup_logging(log_level: str) -> None:
logging.basicConfig(
level=getattr(logging, log_level.upper(), logging.INFO),
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)

1
app/src/core/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Package marker."""

35
app/src/core/config.py Normal file
View File

@@ -0,0 +1,35 @@
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).resolve().parents[2]
ENV_FILE = BASE_DIR / ".env"
load_dotenv(ENV_FILE)
@dataclass(slots=True)
class Settings:
bot_token: str
bot_parse_mode: str
app_env: str
log_level: str
tz: str
def load_settings() -> Settings:
bot_token = os.getenv("BOT_TOKEN", "").strip()
if not bot_token:
raise RuntimeError("BOT_TOKEN is not set in app/.env")
return Settings(
bot_token=bot_token,
bot_parse_mode=os.getenv("BOT_PARSE_MODE", "HTML").strip() or "HTML",
app_env=os.getenv("APP_ENV", "dev").strip() or "dev",
log_level=os.getenv("LOG_LEVEL", "INFO").strip().upper() or "INFO",
tz=os.getenv("TZ", "Europe/Madrid").strip() or "Europe/Madrid",
)

View File

@@ -0,0 +1,2 @@
APP_NAME = "Dzentra Bot"
APP_VERSION = "2.0.0"

View File

@@ -0,0 +1,2 @@
class AppError(Exception):
"""Base application exception."""

View File

@@ -0,0 +1 @@
Здесь будут внешние интеграции, например биржа.

View File

@@ -0,0 +1 @@
"""Package marker."""

12
app/src/main.py Normal file
View File

@@ -0,0 +1,12 @@
import asyncio
from src.bootstrap.app_factory import create_app
async def main() -> None:
bot, dispatcher = create_app()
await dispatcher.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())

1
app/src/shared/README.md Normal file
View File

@@ -0,0 +1 @@
Здесь будут общие утилиты.

View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1 @@
Здесь будет слой доступа к данным.

View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1,12 @@
from aiogram import F, Router
from aiogram.types import Message
from src.telegram.menus import AUTO_TEXT
router = Router(name="auto")
@router.message(F.text == "🤖 Авто")
async def open_auto(message: Message) -> None:
await message.answer(AUTO_TEXT)

View File

@@ -0,0 +1,12 @@
from aiogram import F, Router
from aiogram.types import Message
from src.telegram.menus import HOME_TEXT
router = Router(name="home")
@router.message(F.text == "🏠 Главная")
async def open_home(message: Message) -> None:
await message.answer(HOME_TEXT)

View File

@@ -0,0 +1,12 @@
from aiogram import F, Router
from aiogram.types import Message
from src.telegram.menus import JOURNAL_TEXT
router = Router(name="journal")
@router.message(F.text == "📒 Журнал")
async def open_journal(message: Message) -> None:
await message.answer(JOURNAL_TEXT)

View File

@@ -0,0 +1,12 @@
from aiogram import F, Router
from aiogram.types import Message
from src.telegram.menus import MARKET_TEXT
router = Router(name="market")
@router.message(F.text == "📈 Рынок")
async def open_market(message: Message) -> None:
await message.answer(MARKET_TEXT)

View File

@@ -0,0 +1,12 @@
from aiogram import F, Router
from aiogram.types import Message
from src.telegram.menus import PORTFOLIO_TEXT
router = Router(name="portfolio")
@router.message(F.text == "💼 Портфель")
async def open_portfolio(message: Message) -> None:
await message.answer(PORTFOLIO_TEXT)

View File

@@ -0,0 +1,29 @@
from aiogram import F, Router
from aiogram.filters import Command
from aiogram.types import Message
from src.telegram.keyboards.reply import build_main_menu_keyboard
from src.telegram.menus import MAIN_MENU_TEXT, SYSTEM_TEXT
router = Router(name="start")
@router.message(Command("start"))
async def cmd_start(message: Message) -> None:
await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
@router.message(Command("menu"))
async def cmd_menu(message: Message) -> None:
await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
@router.message(Command("help"))
async def cmd_help(message: Message) -> None:
await message.answer(SYSTEM_TEXT, reply_markup=build_main_menu_keyboard())
@router.message(F.text == "Меню")
async def menu_shortcut(message: Message) -> None:
await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())

View File

@@ -0,0 +1,21 @@
import platform
from aiogram import F, Router
from aiogram.types import Message
from src.core.constants import APP_NAME, APP_VERSION
from src.telegram.menus import SYSTEM_TEXT
router = Router(name="system")
@router.message(F.text.in_({"⚙️ Система", "⚙ Система"}))
async def open_system(message: Message) -> None:
runtime_info = (
"\n\n<b>Runtime</b>\n"
f"- app: {APP_NAME} {APP_VERSION}\n"
f"- python: {platform.python_version()}\n"
f"- os: {platform.system()} {platform.release()}"
)
await message.answer(SYSTEM_TEXT + runtime_info)

View File

@@ -0,0 +1,12 @@
from aiogram import F, Router
from aiogram.types import Message
from src.telegram.menus import TRADE_TEXT
router = Router(name="trade")
@router.message(F.text == "⚡ Торговля")
async def open_trade(message: Message) -> None:
await message.answer(TRADE_TEXT)

View File

@@ -0,0 +1 @@
"""Package marker."""

View File

@@ -0,0 +1,23 @@
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup
def build_main_menu_keyboard() -> ReplyKeyboardMarkup:
return ReplyKeyboardMarkup(
keyboard=[
[
KeyboardButton(text="🏠 Главная"),
KeyboardButton(text="📈 Рынок"),
KeyboardButton(text="💼 Портфель"),
],
[
KeyboardButton(text="⚡ Торговля"),
KeyboardButton(text="🤖 Авто"),
KeyboardButton(text="📒 Журнал"),
],
[
KeyboardButton(text="⚙️ Система"),
],
],
resize_keyboard=True,
input_field_placeholder="Выбери раздел...",
)

30
app/src/telegram/menus.py Normal file
View File

@@ -0,0 +1,30 @@
MAIN_MENU_TEXT = (
"<b>Dzentra Bot</b>\n\n"
"Новый каркас проекта успешно создан.\n\n"
"Выбери раздел через меню ниже."
)
HOME_TEXT = (
"<b>🏠 Главная</b>\n\n"
"Это главный экран бота.\n\n"
"Сейчас здесь отображается базовый статус:\n"
"- бот запущен\n"
"- меню подключено\n"
"- handlers работают\n"
"- проект на этапе Bootstrap v2\n"
)
SYSTEM_TEXT = (
"<b>⚙️ Система</b>\n\n"
"Системный экран.\n\n"
"<b>Справка</b>\n"
"/start — запуск\n"
"/menu — показать меню\n"
"/help — краткая справка\n"
)
MARKET_TEXT = "<b>📈 Рынок</b>\n\nРаздел пока в разработке."
PORTFOLIO_TEXT = "<b>💼 Портфель</b>\n\nРаздел пока в разработке."
TRADE_TEXT = "<b>⚡ Торговля</b>\n\nРаздел пока в разработке."
AUTO_TEXT = "<b>🤖 Авто</b>\n\nРаздел пока в разработке."
JOURNAL_TEXT = "<b>📒 Журнал</b>\n\nРаздел пока в разработке."

View File

@@ -0,0 +1,21 @@
from aiogram import Dispatcher
from src.telegram.handlers.auto import router as auto_router
from src.telegram.handlers.home import router as home_router
from src.telegram.handlers.journal import router as journal_router
from src.telegram.handlers.market import router as market_router
from src.telegram.handlers.portfolio import router as portfolio_router
from src.telegram.handlers.start import router as start_router
from src.telegram.handlers.system import router as system_router
from src.telegram.handlers.trade import router as trade_router
def setup_routers(dispatcher: Dispatcher) -> None:
dispatcher.include_router(start_router)
dispatcher.include_router(home_router)
dispatcher.include_router(market_router)
dispatcher.include_router(portfolio_router)
dispatcher.include_router(trade_router)
dispatcher.include_router(auto_router)
dispatcher.include_router(journal_router)
dispatcher.include_router(system_router)

View File

@@ -0,0 +1 @@
Здесь будет торговая бизнес-логика.

View File

@@ -0,0 +1 @@
"""Package marker."""

1
app/tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Package marker."""

2
app/tests/test_smoke.py Normal file
View File

@@ -0,0 +1,2 @@
def test_smoke() -> None:
assert True