feat(*): init

This commit is contained in:
h
2026-01-20 21:38:01 +01:00
commit b9703da2fc
57 changed files with 3246 additions and 0 deletions

6
backend/.env.example Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
3.13

17
backend/Dockerfile Normal file
View 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
View File

@@ -0,0 +1 @@
# python backend

44
backend/pyproject.toml Normal file
View 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

View 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.[/]")

View File

@@ -0,0 +1,4 @@
from . import main
if __name__ == "__main__":
main()

15
backend/src/bot/common.py Normal file
View 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"]

View File

@@ -0,0 +1 @@
pass

View File

@@ -0,0 +1 @@
pass

View File

@@ -0,0 +1,7 @@
from aiogram import Router
from . import initialize, start
router = Router()
router.include_routers(start.router, initialize.router)

View File

@@ -0,0 +1,3 @@
from .initializer import router
__all__ = ["router"]

View 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...")

View File

@@ -0,0 +1,3 @@
from .start import router
__all__ = ["router"]

View 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")

View File

@@ -0,0 +1 @@
pass

View File

@@ -0,0 +1,4 @@
from .env import env
from .logging import logger
__all__ = ["env", "logger"]

27
backend/src/utils/env.py Normal file
View 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]

View 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

File diff suppressed because it is too large Load Diff