test api
This commit is contained in:
2
api/README.md
Normal file
2
api/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Для запуска API отдельно:
|
||||
# uvicorn api.main:app --reload
|
||||
15
api/config.py
Normal file
15
api/config.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from pydantic import BaseSettings
|
||||
import os
|
||||
|
||||
class Settings(BaseSettings):
|
||||
DB_USER: str = os.getenv("DB_USER", "postgres")
|
||||
DB_PASS: str = os.getenv("DB_PASS", "secretpassword")
|
||||
DB_NAME: str = os.getenv("DB_NAME", "vpn_bot_db")
|
||||
DB_HOST: str = os.getenv("DB_HOST", "db")
|
||||
DB_PORT: str = os.getenv("DB_PORT", "5432")
|
||||
DATABASE_URL: str = os.getenv(
|
||||
"DATABASE_URL",
|
||||
f"postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
||||
)
|
||||
|
||||
settings = Settings()
|
||||
7
api/db/connection.py
Normal file
7
api/db/connection.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from api.config import settings
|
||||
|
||||
DATABASE_URL = settings.DATABASE_URL
|
||||
engine = create_async_engine(DATABASE_URL, echo=False, future=True)
|
||||
SessionLocal = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
|
||||
34
api/db/subscription.py
Normal file
34
api/db/subscription.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from api.db.connection import engine, SessionLocal
|
||||
from api.models.subscription import Subscription
|
||||
from api.models.base import Base
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
async def init_db():
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
async def set_subscription(user_id: int, uuid: str, until: datetime):
|
||||
async with SessionLocal() as session:
|
||||
sub = await session.get(Subscription, user_id)
|
||||
if sub:
|
||||
sub.uuid = uuid
|
||||
sub.until = until
|
||||
else:
|
||||
sub = Subscription(user_id=user_id, uuid=uuid, until=until)
|
||||
session.add(sub)
|
||||
await session.commit()
|
||||
|
||||
async def get_subscription(user_id: int):
|
||||
async with SessionLocal() as session:
|
||||
sub = await session.get(Subscription, user_id)
|
||||
if sub:
|
||||
return {"uuid": sub.uuid, "until": sub.until}
|
||||
return None
|
||||
|
||||
async def extend_subscription(user_id: int, months: int):
|
||||
sub = await get_subscription(user_id)
|
||||
if sub:
|
||||
new_until = max(sub["until"], datetime.now()) + timedelta(days=30*months)
|
||||
await set_subscription(user_id, sub["uuid"], new_until)
|
||||
return new_until
|
||||
return None
|
||||
13
api/main.py
Normal file
13
api/main.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import asyncio
|
||||
from fastapi import FastAPI
|
||||
from api.route import default, subscription
|
||||
from api.db.subscription import init_db
|
||||
|
||||
app = FastAPI(title="VPN Bot API")
|
||||
|
||||
@app.on_event("startup")
|
||||
async def on_startup():
|
||||
await init_db()
|
||||
|
||||
app.include_router(default.router)
|
||||
app.include_router(subscription.router)
|
||||
3
api/models/base.py
Normal file
3
api/models/base.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
8
api/models/subscription.py
Normal file
8
api/models/subscription.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from sqlalchemy import Column, String, DateTime
|
||||
from api.models.base import Base
|
||||
|
||||
class Subscription(Base):
|
||||
__tablename__ = 'subscriptions'
|
||||
uuid = Column(String, primary_key=True, index=True)
|
||||
user_id = Column(String, nullable=False, index=True)
|
||||
until = Column(DateTime, nullable=False)
|
||||
17
api/route/default.py
Normal file
17
api/route/default.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from fastapi import APIRouter
|
||||
import toml
|
||||
from pathlib import Path
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/")
|
||||
async def root():
|
||||
version = "dev"
|
||||
pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
|
||||
if pyproject_path.exists():
|
||||
try:
|
||||
data = toml.load(pyproject_path)
|
||||
version = data.get("project", {}).get("version", "dev")
|
||||
except Exception:
|
||||
pass
|
||||
return {"status": "ok", "msg": "VPN Bot API", "version": version}
|
||||
39
api/route/subscription.py
Normal file
39
api/route/subscription.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from api.db.subscription import get_subscription, set_subscription, extend_subscription
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
router = APIRouter(prefix="/subscription", tags=["subscription"])
|
||||
|
||||
class SubscriptionInfo(BaseModel):
|
||||
user_id: int
|
||||
uuid: str
|
||||
until: datetime
|
||||
|
||||
class SubscriptionCreate(BaseModel):
|
||||
user_id: int
|
||||
months: int
|
||||
|
||||
@router.get("/{user_id}", response_model=Optional[SubscriptionInfo])
|
||||
async def get_user_subscription(user_id: int):
|
||||
sub = await get_subscription(user_id)
|
||||
if not sub:
|
||||
return None
|
||||
return SubscriptionInfo(user_id=user_id, uuid=sub["uuid"], until=sub["until"])
|
||||
|
||||
@router.post("/new", response_model=SubscriptionInfo)
|
||||
async def create_subscription(data: SubscriptionCreate):
|
||||
sub_id = str(uuid.uuid4())
|
||||
until = datetime.now() + timedelta(days=30*data.months)
|
||||
await set_subscription(data.user_id, sub_id, until)
|
||||
return SubscriptionInfo(user_id=data.user_id, uuid=sub_id, until=until)
|
||||
|
||||
@router.post("/extend", response_model=SubscriptionInfo)
|
||||
async def extend_user_subscription(data: SubscriptionCreate):
|
||||
new_until = await extend_subscription(data.user_id, data.months)
|
||||
if not new_until:
|
||||
raise HTTPException(status_code=404, detail="Subscription not found")
|
||||
sub = await get_subscription(data.user_id)
|
||||
return SubscriptionInfo(user_id=data.user_id, uuid=sub["uuid"], until=new_until)
|
||||
25
bot/database/subscription_db.py
Normal file
25
bot/database/subscription_db.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import aiohttp
|
||||
|
||||
API_URL = "http://api:8000/subscription"
|
||||
|
||||
async def get_subscription(user_id: int):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(f"{API_URL}/{user_id}") as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
if data:
|
||||
data["until"] = datetime.fromisoformat(data["until"])
|
||||
return data
|
||||
return None
|
||||
|
||||
async def set_subscription(user_id: int, uuid: str, until: datetime, months: int):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
payload = {"user_id": user_id, "months": months}
|
||||
async with session.post(f"{API_URL}/new", json=payload) as resp:
|
||||
return await resp.json()
|
||||
|
||||
async def extend_subscription(user_id: int, months: int):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
payload = {"user_id": user_id, "months": months}
|
||||
async with session.post(f"{API_URL}/extend", json=payload) as resp:
|
||||
return await resp.json()
|
||||
@@ -2,23 +2,20 @@ from aiogram import Router, types
|
||||
from aiogram.filters import CommandStart
|
||||
from bot.handlers import payments
|
||||
from bot.handlers.payments import send_donation_invoice
|
||||
from bot.handlers.cabinet import router as cabinet_router
|
||||
from bot.keyboards.subscription import subscription_keyboard
|
||||
|
||||
router = Router()
|
||||
router.include_router(payments.router)
|
||||
router.include_router(cabinet_router)
|
||||
|
||||
@router.message(CommandStart())
|
||||
async def cmd_start(message: types.Message):
|
||||
await message.answer(f"""Привет, {message.from_user.full_name}!
|
||||
⭐ Поддержать проект
|
||||
|
||||
Если вам нравится проект и вы хотите помочь его развитию, вы можете отправить звёзды в качестве добровольной поддержки.
|
||||
|
||||
Пожертвования помогают покрывать:
|
||||
— еду автору
|
||||
— аренду серверов
|
||||
— развитие и улучшение проекта
|
||||
|
||||
Поддержка полностью добровольная и не является обязательным условием получения доступа.""")
|
||||
await message.answer(
|
||||
f"""Привет, {message.from_user.full_name}!
|
||||
⭐ Поддержать проект\n\nЕсли вам нравится проект и вы хотите помочь его развитию, вы можете отправить звёзды или оформить подписку.\n\nПожертвования помогают покрывать:\n— еду автору\n— аренду серверов\n— развитие и улучшение проекта\n\nПоддержка полностью добровольная и не является обязательным условием получения доступа.\n\nВыберите вариант подписки:""",
|
||||
reply_markup=subscription_keyboard
|
||||
)
|
||||
|
||||
# Отправляем кнопку оплаты (инвойс) сразу после текста
|
||||
await send_donation_invoice(message)
|
||||
|
||||
57
bot/handlers/cabinet.py
Normal file
57
bot/handlers/cabinet.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from aiogram import Router, types, F
|
||||
from aiogram.filters import Command
|
||||
from bot.keyboards.subscription import subscription_keyboard, cabinet_keyboard
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from bot.database.subscription_db import init_db, set_subscription, get_subscription, extend_subscription
|
||||
|
||||
init_db()
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.message(Command("cabinet"))
|
||||
async def cabinet(message: types.Message):
|
||||
user_id = message.from_user.id
|
||||
sub = get_subscription(user_id)
|
||||
if sub:
|
||||
await message.answer(
|
||||
f"🗂 Личный кабинет\n"
|
||||
f"Статус: Активна\n"
|
||||
f"UUID: {sub['uuid']}\n"
|
||||
f"Действует до: {sub['until'].strftime('%d.%m.%Y')}\n",
|
||||
reply_markup=cabinet_keyboard
|
||||
)
|
||||
else:
|
||||
await message.answer("У вас нет активной подписки.")
|
||||
|
||||
@router.callback_query(F.data.startswith("sub_"))
|
||||
async def on_subscribe(callback: types.CallbackQuery):
|
||||
user_id = callback.from_user.id
|
||||
period_map = {
|
||||
"sub_1m": 1,
|
||||
"sub_3m": 3,
|
||||
"sub_6m": 6,
|
||||
"sub_12m": 12,
|
||||
}
|
||||
period = period_map.get(callback.data)
|
||||
if not period:
|
||||
await callback.answer("Ошибка выбора тарифа", show_alert=True)
|
||||
return
|
||||
# Генерируем uuid и дату окончания
|
||||
import uuid as uuidlib
|
||||
sub_id = str(uuidlib.uuid4())
|
||||
until = datetime.now() + timedelta(days=30*period)
|
||||
set_subscription(user_id, sub_id, until)
|
||||
await callback.message.answer(
|
||||
f"Вы выбрали подписку на {period} мес.\n\nПерейдите к оплате (будет реализовано позже)")
|
||||
await callback.answer()
|
||||
|
||||
@router.callback_query(F.data == "extend_sub")
|
||||
async def on_extend(callback: types.CallbackQuery):
|
||||
user_id = callback.from_user.id
|
||||
sub = get_subscription(user_id)
|
||||
if not sub:
|
||||
await callback.answer("Нет активной подписки", show_alert=True)
|
||||
return
|
||||
await callback.message.answer("Выберите период продления:", reply_markup=subscription_keyboard)
|
||||
await callback.answer()
|
||||
22
bot/keyboards/subscription.py
Normal file
22
bot/keyboards/subscription.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
# Клавиатура выбора тарифа
|
||||
subscription_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(text="1 месяц", callback_data="sub_1m"),
|
||||
InlineKeyboardButton(text="3 месяца", callback_data="sub_3m"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(text="6 месяцев", callback_data="sub_6m"),
|
||||
InlineKeyboardButton(text="12 месяцев", callback_data="sub_12m"),
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
# Клавиатура личного кабинета
|
||||
cabinet_keyboard = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Продлить подписку", callback_data="extend_sub")]
|
||||
]
|
||||
)
|
||||
20
docker-compose.override.yml
Normal file
20
docker-compose.override.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
bot:
|
||||
build: .
|
||||
command: python -m bot.main
|
||||
volumes:
|
||||
- .:/app
|
||||
env_file:
|
||||
- .env
|
||||
restart: unless-stopped
|
||||
api:
|
||||
build: .
|
||||
command: uvicorn api.main:app --host 0.0.0.0 --port 8000
|
||||
volumes:
|
||||
- .:/app
|
||||
env_file:
|
||||
- .env
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- bot
|
||||
@@ -6,6 +6,11 @@ readme = "README.md"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"aiogram>=3.25.0",
|
||||
"aiosqlite>=0.22.1",
|
||||
"asyncio>=4.0.0",
|
||||
"fastapi>=0.129.0",
|
||||
"logging>=0.4.9.6",
|
||||
"pydantic>=2.12.5",
|
||||
"sqlalchemy>=2.0.46",
|
||||
"toml>=0.10.2",
|
||||
]
|
||||
|
||||
@@ -2,3 +2,6 @@ aiogram>=3.0
|
||||
asyncpg
|
||||
sqlalchemy
|
||||
python-dotenv
|
||||
fastapi
|
||||
uvicorn
|
||||
pydantic
|
||||
|
||||
126
uv.lock
generated
126
uv.lock
generated
@@ -100,6 +100,24 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
version = "0.22.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-doc"
|
||||
version = "0.0.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
@@ -109,6 +127,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asyncio"
|
||||
version = "4.0.0"
|
||||
@@ -136,6 +166,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.129.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/47/75f6bea02e797abff1bca968d5997793898032d9923c1935ae2efdece642/fastapi-0.129.0.tar.gz", hash = "sha256:61315cebd2e65df5f97ec298c888f9de30430dd0612d59d6480beafbc10655af", size = 375450, upload-time = "2026-02-12T13:54:52.541Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/dd/d0ee25348ac58245ee9f90b6f3cbb666bf01f69be7e0911f9851bddbda16/fastapi-0.129.0-py3-none-any.whl", hash = "sha256:b4946880e48f462692b31c083be0432275cbfb6e2274566b1be91479cc1a84ec", size = 102950, upload-time = "2026-02-12T13:54:54.528Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
version = "1.8.0"
|
||||
@@ -177,6 +223,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
@@ -339,21 +410,76 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.46"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.52.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "telegram-bot-vpn"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "aiogram" },
|
||||
{ name = "aiosqlite" },
|
||||
{ name = "asyncio" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "logging" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "toml" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aiogram", specifier = ">=3.25.0" },
|
||||
{ name = "aiosqlite", specifier = ">=0.22.1" },
|
||||
{ name = "asyncio", specifier = ">=4.0.0" },
|
||||
{ name = "fastapi", specifier = ">=0.129.0" },
|
||||
{ name = "logging", specifier = ">=0.4.9.6" },
|
||||
{ name = "pydantic", specifier = ">=2.12.5" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.46" },
|
||||
{ name = "toml", specifier = ">=0.10.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user