feat(*): init
This commit is contained in:
6
backend/.env.example
Normal file
6
backend/.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
BOT__TOKEN=<BOT__TOKEN>
|
||||
|
||||
LOG__LEVEL=INFO
|
||||
LOG__LEVEL_EXTERNAL=WARNING
|
||||
LOG__SHOW_TIME=false
|
||||
LOG__CONSOLE_WIDTH=150
|
||||
20
backend/.gitignore
vendored
Normal file
20
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Research and Tests
|
||||
t
|
||||
1
backend/.python-version
Normal file
1
backend/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
17
backend/Dockerfile
Normal file
17
backend/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-alpine
|
||||
|
||||
ENV TERM=xterm-256color
|
||||
ENV COLORTERM=truecolor
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ADD pyproject.toml uv.lock ./
|
||||
RUN uv sync --frozen --no-install-project --no-dev
|
||||
|
||||
COPY /src ./src
|
||||
|
||||
RUN uv sync --frozen --no-dev
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
ENTRYPOINT ["python", "-m"]
|
||||
1
backend/README.md
Normal file
1
backend/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# python backend
|
||||
44
backend/pyproject.toml
Normal file
44
backend/pyproject.toml
Normal file
@@ -0,0 +1,44 @@
|
||||
[project]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
description = "Backend for stealth-ai-relay"
|
||||
authors = [
|
||||
{ name = "h", email = "h@kotikot.com" }
|
||||
]
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"aiogram>=3.24.0",
|
||||
"convex>=0.7.0",
|
||||
"pydantic-ai-slim[google]>=1.44.0",
|
||||
"pydantic-settings>=2.12.0",
|
||||
"rich>=14.2.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.9.13,<0.10.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[tool.uv.build-backend]
|
||||
module-name = ["bot"]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py313"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["ALL"]
|
||||
ignore = ["CPY", "D1", "D203", "D212", "COM812", "RUF001", "RUF002", "RUF003"]
|
||||
unfixable = ["F401"]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"*.lock" = ["ALL"]
|
||||
"docs/*" = ["ALL"]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[tool.ruff.format]
|
||||
docstring-code-format = true
|
||||
skip-magic-trailing-comma = true
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
split-on-trailing-comma = false
|
||||
33
backend/src/bot/__init__.py
Normal file
33
backend/src/bot/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
|
||||
from rich import traceback
|
||||
|
||||
from utils.logging import logger, setup_logging
|
||||
|
||||
setup_logging()
|
||||
|
||||
|
||||
async def runner() -> None:
|
||||
from . import handlers # noqa: PLC0415
|
||||
from .common import bot, dp # noqa: PLC0415
|
||||
|
||||
dp.include_routers(handlers.router)
|
||||
|
||||
await bot.delete_webhook(drop_pending_updates=True)
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
def plugins() -> None:
|
||||
traceback.install(show_locals=True)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
plugins()
|
||||
|
||||
logger.info("Starting...")
|
||||
|
||||
with contextlib.suppress(KeyboardInterrupt):
|
||||
asyncio.run(runner())
|
||||
|
||||
logger.info("[red]Stopped.[/]")
|
||||
4
backend/src/bot/__main__.py
Normal file
4
backend/src/bot/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
15
backend/src/bot/common.py
Normal file
15
backend/src/bot/common.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
|
||||
from utils import env
|
||||
|
||||
bot = Bot(
|
||||
token=env.bot.token.get_secret_value(),
|
||||
default=DefaultBotProperties(parse_mode="HTML"),
|
||||
)
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(storage=storage)
|
||||
|
||||
|
||||
__all__ = ["bot", "dp"]
|
||||
1
backend/src/bot/factories/__init__.py
Normal file
1
backend/src/bot/factories/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
pass
|
||||
1
backend/src/bot/filters/__init__.py
Normal file
1
backend/src/bot/filters/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
pass
|
||||
7
backend/src/bot/handlers/__init__.py
Normal file
7
backend/src/bot/handlers/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from aiogram import Router
|
||||
|
||||
from . import initialize, start
|
||||
|
||||
router = Router()
|
||||
|
||||
router.include_routers(start.router, initialize.router)
|
||||
3
backend/src/bot/handlers/initialize/__init__.py
Normal file
3
backend/src/bot/handlers/initialize/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .initializer import router
|
||||
|
||||
__all__ = ["router"]
|
||||
18
backend/src/bot/handlers/initialize/initializer.py
Normal file
18
backend/src/bot/handlers/initialize/initializer.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from aiogram import Bot, Router, types
|
||||
|
||||
from utils.logging import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.startup()
|
||||
async def startup(bot: Bot) -> None:
|
||||
await bot.set_my_commands(
|
||||
[types.BotCommand(command="/start", description="Start bot")]
|
||||
)
|
||||
logger.info(f"[green]Started as[/] @{(await bot.me()).username}")
|
||||
|
||||
|
||||
@router.shutdown()
|
||||
async def shutdown() -> None:
|
||||
logger.info("Shutting down bot...")
|
||||
3
backend/src/bot/handlers/start/__init__.py
Normal file
3
backend/src/bot/handlers/start/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .start import router
|
||||
|
||||
__all__ = ["router"]
|
||||
9
backend/src/bot/handlers/start/start.py
Normal file
9
backend/src/bot/handlers/start/start.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from aiogram import Router, types
|
||||
from aiogram.filters import CommandStart
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
async def on_start(message: types.Message) -> None:
|
||||
await message.answer("hi")
|
||||
1
backend/src/bot/keyboards/__init__.py
Normal file
1
backend/src/bot/keyboards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
pass
|
||||
4
backend/src/utils/__init__.py
Normal file
4
backend/src/utils/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .env import env
|
||||
from .logging import logger
|
||||
|
||||
__all__ = ["env", "logger"]
|
||||
27
backend/src/utils/env.py
Normal file
27
backend/src/utils/env.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pydantic import AliasChoices, Field, SecretStr
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class BotSettings(BaseSettings):
|
||||
token: SecretStr
|
||||
|
||||
|
||||
class LogSettings(BaseSettings):
|
||||
level: str = "INFO"
|
||||
level_external: str = "WARNING"
|
||||
show_time: bool = False
|
||||
console_width: int = 150
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
bot: BotSettings
|
||||
log: LogSettings
|
||||
|
||||
convex_url: str = Field(validation_alias=AliasChoices("CONVEX_SELF_HOSTED_URL"))
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
case_sensitive=False, env_file=".env", env_nested_delimiter="__", extra="ignore"
|
||||
)
|
||||
|
||||
|
||||
env = Settings() # ty:ignore[missing-argument]
|
||||
35
backend/src/utils/logging.py
Normal file
35
backend/src/utils/logging.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import logging
|
||||
|
||||
from aiogram.dispatcher import router
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
from rich.traceback import install
|
||||
|
||||
from .env import env
|
||||
|
||||
console = Console(width=env.log.console_width, color_system="auto", force_terminal=True)
|
||||
|
||||
|
||||
def setup_logging() -> None:
|
||||
logging.basicConfig(
|
||||
level=env.log.level_external,
|
||||
format="",
|
||||
datefmt=None,
|
||||
handlers=[
|
||||
RichHandler(
|
||||
console=console,
|
||||
markup=True,
|
||||
rich_tracebacks=True,
|
||||
enable_link_path=False,
|
||||
tracebacks_show_locals=True,
|
||||
omit_repeated_times=False,
|
||||
show_time=env.log.show_time,
|
||||
tracebacks_suppress=[router],
|
||||
)
|
||||
],
|
||||
)
|
||||
install(console=console, show_locals=True)
|
||||
|
||||
|
||||
logger = logging.getLogger("telegram-casino")
|
||||
logger.setLevel(env.log.level)
|
||||
1022
backend/uv.lock
generated
Normal file
1022
backend/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user