# ruff: noqa: F401,F403,F405,I001 import asyncio import base64 import hashlib import html import hmac import io import ipaddress import json import logging import os import re import secrets import socket import subprocess import time from collections import deque from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List, Optional, Tuple from urllib.parse import parse_qsl, quote, urlencode, urlsplit, urlunsplit from aiogram import Bot, Dispatcher from aiogram.types import LabeledPrice from aiohttp import ClientSession, ClientTimeout, web from pydantic import BaseModel, ConfigDict, EmailStr, ValidationError, constr, field_validator from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import sessionmaker from bot.app.web.admin_api import ( admin_auth_middleware, setup_admin_routes, ) from bot.app.web.webapp_auth import ( create_signed_telegram_oauth_state, create_telegram_oauth_nonce, create_webapp_session_token, validate_telegram_login_widget_data, validate_telegram_oauth_id_token, validate_telegram_webapp_init_data, verify_signed_telegram_oauth_state, verify_telegram_oauth_nonce, verify_webapp_session_token, ) from bot.infra.redis import cache_get_json, cache_set_json, get_redis, redis_key from bot.services.crypto_pay_service import CryptoPayService from bot.services.email_auth_service import EmailAuthService, normalize_email from bot.services.email_templates import render_account_merged from bot.services.freekassa_service import FreeKassaService from bot.services.platega_service import PlategaService from bot.services.promo_code_service import PromoCodeService from bot.services.referral_service import ReferralService from bot.services.severpay_service import SeverPayService from bot.services.subscription_service import SubscriptionService from bot.services.wata_service import WataService from bot.services.yookassa_service import YooKassaService from bot.utils.config_link import prepare_config_links from bot.utils.request_security import parse_ip_entries, request_client_ip from bot.utils.text_sanitizer import sanitize_display_name, sanitize_username from config.settings import Settings from db.dal import payment_dal, subscription_dal, user_dal from db.dal.user_dal import UserMergeConflictError from db.models import Payment, User, UserTelegramAvatar logger = logging.getLogger(__name__) TEMPLATE_PATH = Path(__file__).resolve().parents[1] / "templates" / "subscription_webapp.html" ASSET_DIR = TEMPLATE_PATH.parent APP_ROOT = Path(__file__).resolve().parents[5] WEBAPP_LOGO_PROXY_PATH = "/webapp-logo" WEBAPP_LOGO_CACHE_DIR = APP_ROOT / "data" / "webapp-logo" WEBAPP_UPLOADED_LOGO_DIR = WEBAPP_LOGO_CACHE_DIR / "uploads" WEBAPP_UPLOADED_LOGO_PATH = "/webapp-uploaded-logo" WEBAPP_FAVICON_DIR = WEBAPP_LOGO_CACHE_DIR / "favicons" WEBAPP_FAVICON_PATH = "/webapp-favicon" WEBAPP_EMOJI_CACHE_DIR = APP_ROOT / "data" / "webapp-emoji" WEBAPP_CONFIG_PLACEHOLDER = "" WEBAPP_I18N_PLACEHOLDER = "" WEBAPP_JS_PLACEHOLDER = "" APP_REPOSITORY_URL = "https://github.com/3252a8/remnawave-minishop" DEV_MOCK_START_MARKER = "" DEV_MOCK_END_MARKER = "" WEBAPP_RATE_LIMIT_WINDOW_SECONDS = 60 WEBAPP_RATE_LIMIT_MAX_REQUESTS = 30 WEBAPP_LOGO_MAX_BYTES = 2 * 1024 * 1024 WEBAPP_EMOJI_MAX_BYTES = 4 * 1024 * 1024 WEBAPP_THEME_CSS_MAX_BYTES = 512 * 1024 WEBAPP_THEME_ASSET_MAX_BYTES = 1024 * 1024 WEBAPP_THEME_ASSET_CONTENT_TYPES = { ".gif": "image/gif", ".ico": "image/x-icon", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp", } WEBAPP_TELEGRAM_AVATAR_MAX_BYTES = 128 * 1024 WEBAPP_TELEGRAM_AVATAR_REFRESH_SECONDS = 24 * 60 * 60 WEBAPP_TELEGRAM_AVATAR_FETCH_TIMEOUT_SECONDS = 4 WEBAPP_SESSION_COOKIE_NAME = "rw_webapp_session" WEBAPP_CSRF_COOKIE_NAME = "rw_webapp_csrf" WEBAPP_TELEGRAM_OAUTH_STATE_COOKIE_NAME = "rw_tg_oauth_state" WEBAPP_CSRF_HEADER_NAME = "X-CSRF-Token" WEBAPP_STATE_CHANGING_METHODS = {"POST", "PUT", "PATCH", "DELETE"} _APP_VERSION_CACHE: Optional[str] = None WEBAPP_CSRF_EXEMPT_PATHS = { "/api/auth/telegram/nonce", "/api/auth/token", "/api/auth/email/request", "/api/auth/email/verify", "/api/auth/email/magic", "/api/auth/logout", } _SHARED_HTTP_SESSION: Optional[ClientSession] = None _SHARED_HTTP_SESSION_LOCK = asyncio.Lock() __all__ = [name for name in globals() if not name.startswith("__")]