mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-04 12:55:10 +00:00
ci: add ruff lint workflow and fix formatting
This commit is contained in:
27
.github/workflows/lint.yml
vendored
Normal file
27
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
branches: ['**']
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
- run: uv sync --group dev
|
||||
|
||||
- name: Check formatting
|
||||
run: uv run ruff format --check .
|
||||
|
||||
- name: Check linting
|
||||
run: uv run ruff check .
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -31,6 +31,13 @@ docker-compose.override.yml
|
||||
# Разрешаем .gitignore чтобы он попал в репозиторий
|
||||
!.gitignore
|
||||
|
||||
# Разрешаем .github/ (workflows, pre-commit и т.д.)
|
||||
!.github/
|
||||
!.github/**
|
||||
|
||||
# Разрешаем Makefile
|
||||
!Makefile
|
||||
|
||||
# Внутри разрешенных папок игнорируем служебные файлы
|
||||
app/__pycache__/
|
||||
app/**/__pycache__/
|
||||
|
||||
4
Makefile
4
Makefile
@@ -45,7 +45,5 @@ help: ## Показать список доступных команд
|
||||
@echo ""
|
||||
@echo "📘 Команды Makefile:"
|
||||
@echo ""
|
||||
@grep -E '^[a-zA-Z0-9_-]+:.*?##' $(MAKEFILE_LIST) | \
|
||||
sed -E 's/:.*?## /| /' | \
|
||||
awk -F'|' '{printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
|
||||
@awk -F':.*## ' '/^[a-zA-Z0-9_-]+:.*## / {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
@echo ""
|
||||
|
||||
@@ -147,7 +147,7 @@ async def get_current_cabinet_user(
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning(f'Timeout checking channel subscription for user {user.telegram_id}')
|
||||
# Don't block user if check times out
|
||||
except Exception as e:
|
||||
|
||||
@@ -3,16 +3,22 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
from app.database.crud.user import create_user, create_user_by_email, get_user_by_id, get_user_by_referral_code, get_user_by_telegram_id
|
||||
from app.services.referral_service import process_referral_registration
|
||||
from app.database.crud.user import (
|
||||
create_user,
|
||||
create_user_by_email,
|
||||
get_user_by_id,
|
||||
get_user_by_referral_code,
|
||||
get_user_by_telegram_id,
|
||||
)
|
||||
from app.database.models import CabinetRefreshToken, User
|
||||
from app.services.referral_service import process_referral_registration
|
||||
|
||||
from ..auth import (
|
||||
create_access_token,
|
||||
@@ -161,18 +167,16 @@ async def _sync_subscription_from_panel_by_email(db: AsyncSession, user: User) -
|
||||
traffic_used_gb = panel_user.used_traffic_bytes / (1024**3) if panel_user.used_traffic_bytes > 0 else 0
|
||||
|
||||
# Extract squad UUIDs from active_internal_squads
|
||||
connected_squads = [
|
||||
s.get('uuid', '') for s in (panel_user.active_internal_squads or []) if s.get('uuid')
|
||||
]
|
||||
connected_squads = [s.get('uuid', '') for s in (panel_user.active_internal_squads or []) if s.get('uuid')]
|
||||
|
||||
# Device limit from panel
|
||||
device_limit = panel_user.hwid_device_limit or 1
|
||||
|
||||
# Determine status - use timezone-aware datetime for comparison
|
||||
current_time = datetime.now(timezone.utc)
|
||||
current_time = datetime.now(UTC)
|
||||
# Make expire_at timezone-aware if it's naive
|
||||
if expire_at.tzinfo is None:
|
||||
expire_at = expire_at.replace(tzinfo=timezone.utc)
|
||||
expire_at = expire_at.replace(tzinfo=UTC)
|
||||
|
||||
if panel_user.status.value == 'ACTIVE' and expire_at > current_time:
|
||||
sub_status = SubscriptionStatus.ACTIVE
|
||||
@@ -195,7 +199,9 @@ async def _sync_subscription_from_panel_by_email(db: AsyncSession, user: User) -
|
||||
existing_sub.connected_squads = connected_squads
|
||||
existing_sub.device_limit = device_limit
|
||||
existing_sub.is_trial = False # Panel subscription is not trial
|
||||
logger.info(f'Updated subscription for email user {user.email}, squads: {connected_squads}, devices: {device_limit}')
|
||||
logger.info(
|
||||
f'Updated subscription for email user {user.email}, squads: {connected_squads}, devices: {device_limit}'
|
||||
)
|
||||
else:
|
||||
# Create new subscription
|
||||
# Convert current_time to naive for database storage if needed
|
||||
@@ -216,7 +222,9 @@ async def _sync_subscription_from_panel_by_email(db: AsyncSession, user: User) -
|
||||
device_limit=device_limit,
|
||||
)
|
||||
db.add(new_sub)
|
||||
logger.info(f'Created subscription for email user {user.email}, squads: {connected_squads}, devices: {device_limit}')
|
||||
logger.info(
|
||||
f'Created subscription for email user {user.email}, squads: {connected_squads}, devices: {device_limit}'
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
@@ -469,7 +477,9 @@ async def register_email_standalone(
|
||||
logger.warning(f'Self-referral attempt blocked: email={request.email}, code={request.referral_code}')
|
||||
referrer = None
|
||||
else:
|
||||
logger.info(f'Found referrer for email registration: referrer_id={referrer.id}, code={request.referral_code}')
|
||||
logger.info(
|
||||
f'Found referrer for email registration: referrer_id={referrer.id}, code={request.referral_code}'
|
||||
)
|
||||
|
||||
# Создать пользователя
|
||||
user = await create_user_by_email(
|
||||
|
||||
@@ -1642,9 +1642,9 @@ async def purchase_tariff(
|
||||
if not user.telegram_id and user.email and user.email_verified:
|
||||
try:
|
||||
# Determine if this is a new subscription or extension
|
||||
was_new_subscription = subscription.start_date and (
|
||||
datetime.utcnow() - subscription.start_date
|
||||
).total_seconds() < 60
|
||||
was_new_subscription = (
|
||||
subscription.start_date and (datetime.utcnow() - subscription.start_date).total_seconds() < 60
|
||||
)
|
||||
notification_type = (
|
||||
NotificationType.SUBSCRIPTION_ACTIVATED
|
||||
if was_new_subscription
|
||||
|
||||
@@ -41,9 +41,7 @@ class EmailService:
|
||||
if smtp.has_extn('auth'):
|
||||
smtp.login(self.user, self.password)
|
||||
else:
|
||||
logger.debug(
|
||||
f'SMTP server {self.host} does not support AUTH, skipping authentication'
|
||||
)
|
||||
logger.debug(f'SMTP server {self.host} does not support AUTH, skipping authentication')
|
||||
|
||||
return smtp
|
||||
|
||||
|
||||
@@ -1886,9 +1886,7 @@ async def get_disabled_daily_subscriptions_for_resume(
|
||||
result = await db.execute(query)
|
||||
subscriptions = result.scalars().all()
|
||||
|
||||
logger.info(
|
||||
f"🔍 Найдено {len(subscriptions)} DISABLED суточных подписок для возобновления"
|
||||
)
|
||||
logger.info(f'🔍 Найдено {len(subscriptions)} DISABLED суточных подписок для возобновления')
|
||||
|
||||
return list(subscriptions)
|
||||
|
||||
|
||||
@@ -6079,7 +6079,7 @@ async def migrate_cloudpayments_transaction_id_to_bigint() -> bool:
|
||||
try:
|
||||
table_exists = await check_table_exists('cloudpayments_payments')
|
||||
if not table_exists:
|
||||
logger.info("ℹ️ Таблица cloudpayments_payments не существует, пропускаем миграцию")
|
||||
logger.info('ℹ️ Таблица cloudpayments_payments не существует, пропускаем миграцию')
|
||||
return True
|
||||
|
||||
db_type = await get_database_type()
|
||||
@@ -6087,51 +6087,53 @@ async def migrate_cloudpayments_transaction_id_to_bigint() -> bool:
|
||||
async with engine.begin() as conn:
|
||||
if db_type == 'postgresql':
|
||||
# Проверяем текущий тип колонки
|
||||
result = await conn.execute(text("""
|
||||
result = await conn.execute(
|
||||
text("""
|
||||
SELECT data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'cloudpayments_payments'
|
||||
AND column_name = 'transaction_id_cp'
|
||||
"""))
|
||||
""")
|
||||
)
|
||||
row = result.fetchone()
|
||||
|
||||
if row and row[0] == 'bigint':
|
||||
logger.info("ℹ️ Колонка transaction_id_cp уже имеет тип BIGINT")
|
||||
logger.info('ℹ️ Колонка transaction_id_cp уже имеет тип BIGINT')
|
||||
return True
|
||||
|
||||
# Меняем тип на BIGINT
|
||||
await conn.execute(text(
|
||||
"ALTER TABLE cloudpayments_payments ALTER COLUMN transaction_id_cp TYPE BIGINT"
|
||||
))
|
||||
logger.info("✅ Колонка transaction_id_cp изменена на BIGINT")
|
||||
await conn.execute(
|
||||
text('ALTER TABLE cloudpayments_payments ALTER COLUMN transaction_id_cp TYPE BIGINT')
|
||||
)
|
||||
logger.info('✅ Колонка transaction_id_cp изменена на BIGINT')
|
||||
|
||||
elif db_type == 'mysql':
|
||||
# Проверяем текущий тип колонки
|
||||
result = await conn.execute(text("""
|
||||
result = await conn.execute(
|
||||
text("""
|
||||
SELECT DATA_TYPE
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = 'cloudpayments_payments'
|
||||
AND COLUMN_NAME = 'transaction_id_cp'
|
||||
"""))
|
||||
""")
|
||||
)
|
||||
row = result.fetchone()
|
||||
|
||||
if row and row[0].lower() == 'bigint':
|
||||
logger.info("ℹ️ Колонка transaction_id_cp уже имеет тип BIGINT")
|
||||
logger.info('ℹ️ Колонка transaction_id_cp уже имеет тип BIGINT')
|
||||
return True
|
||||
|
||||
await conn.execute(text(
|
||||
"ALTER TABLE cloudpayments_payments MODIFY transaction_id_cp BIGINT"
|
||||
))
|
||||
logger.info("✅ Колонка transaction_id_cp изменена на BIGINT")
|
||||
await conn.execute(text('ALTER TABLE cloudpayments_payments MODIFY transaction_id_cp BIGINT'))
|
||||
logger.info('✅ Колонка transaction_id_cp изменена на BIGINT')
|
||||
|
||||
elif db_type == 'sqlite':
|
||||
# SQLite не поддерживает ALTER COLUMN, но INTEGER в SQLite уже 64-bit
|
||||
logger.info("ℹ️ SQLite использует 64-bit INTEGER по умолчанию, миграция не требуется")
|
||||
logger.info('ℹ️ SQLite использует 64-bit INTEGER по умолчанию, миграция не требуется')
|
||||
|
||||
return True
|
||||
|
||||
except Exception as error:
|
||||
logger.error(f"❌ Ошибка миграции transaction_id_cp на BIGINT: {error}")
|
||||
logger.error(f'❌ Ошибка миграции transaction_id_cp на BIGINT: {error}')
|
||||
return False
|
||||
|
||||
|
||||
@@ -6766,12 +6768,12 @@ async def run_universal_migration():
|
||||
else:
|
||||
logger.warning('⚠️ Проблемы с таблицами колеса удачи')
|
||||
|
||||
logger.info("=== МИГРАЦИЯ CLOUDPAYMENTS TRANSACTION_ID НА BIGINT ===")
|
||||
logger.info('=== МИГРАЦИЯ CLOUDPAYMENTS TRANSACTION_ID НА BIGINT ===')
|
||||
cloudpayments_bigint_ready = await migrate_cloudpayments_transaction_id_to_bigint()
|
||||
if cloudpayments_bigint_ready:
|
||||
logger.info("✅ Колонка transaction_id_cp в cloudpayments_payments обновлена до BIGINT")
|
||||
logger.info('✅ Колонка transaction_id_cp в cloudpayments_payments обновлена до BIGINT')
|
||||
else:
|
||||
logger.warning("⚠️ Проблемы с миграцией transaction_id_cp")
|
||||
logger.warning('⚠️ Проблемы с миграцией transaction_id_cp')
|
||||
|
||||
async with engine.begin() as conn:
|
||||
total_subs = await conn.execute(text('SELECT COUNT(*) FROM subscriptions'))
|
||||
|
||||
6
app/external/remnawave_api.py
vendored
6
app/external/remnawave_api.py
vendored
@@ -341,7 +341,11 @@ class RemnaWaveAPI:
|
||||
|
||||
connector = aiohttp.TCPConnector(**connector_kwargs)
|
||||
|
||||
session_kwargs = {'timeout': aiohttp.ClientTimeout(total=60, connect=10), 'headers': headers, 'connector': connector}
|
||||
session_kwargs = {
|
||||
'timeout': aiohttp.ClientTimeout(total=60, connect=10),
|
||||
'headers': headers,
|
||||
'connector': connector,
|
||||
}
|
||||
|
||||
if cookies:
|
||||
session_kwargs['cookies'] = cookies
|
||||
|
||||
@@ -202,7 +202,7 @@ class NotificationDeliveryService:
|
||||
)
|
||||
return True
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning(
|
||||
'Timeout при отправке Telegram уведомления пользователю %s',
|
||||
user.telegram_id,
|
||||
|
||||
@@ -1174,10 +1174,7 @@ class RemnaWaveService:
|
||||
user.remnawave_uuid: user for user in bot_users if getattr(user, 'remnawave_uuid', None)
|
||||
}
|
||||
# Index users by email for email-only sync
|
||||
bot_users_by_email = {
|
||||
user.email.lower(): user for user in bot_users
|
||||
if user.email and user.email_verified
|
||||
}
|
||||
bot_users_by_email = {user.email.lower(): user for user in bot_users if user.email and user.email_verified}
|
||||
# Also index email-only users by their remnawave_uuid for sync
|
||||
email_users_count = sum(1 for u in bot_users if u.telegram_id is None)
|
||||
if email_users_count > 0:
|
||||
@@ -1203,8 +1200,7 @@ class RemnaWaveService:
|
||||
|
||||
# Email-only пользователи из панели (без telegram_id, но с email)
|
||||
panel_users_email_only = [
|
||||
user for user in panel_users
|
||||
if user.get('telegramId') is None and user.get('email')
|
||||
user for user in panel_users if user.get('telegramId') is None and user.get('email')
|
||||
]
|
||||
if panel_users_email_only:
|
||||
logger.info(f'📧 Пользователей в панели с Email (без Telegram): {len(panel_users_email_only)}')
|
||||
@@ -1834,7 +1830,10 @@ class RemnaWaveService:
|
||||
telegram_id=user.telegram_id,
|
||||
email=user.email,
|
||||
description=settings.format_remnawave_user_description(
|
||||
full_name=user.full_name, username=user.username, telegram_id=user.telegram_id, email=user.email
|
||||
full_name=user.full_name,
|
||||
username=user.username,
|
||||
telegram_id=user.telegram_id,
|
||||
email=user.email,
|
||||
),
|
||||
active_internal_squads=sub.connected_squads,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user