add_change_domain/bug_fixes_16

This commit is contained in:
Vladless
2025-02-18 02:05:14 +03:00
parent f799dd8766
commit 4afd1668c9
13 changed files with 3775 additions and 5006 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTM
storage = MemoryStorage()
dp = Dispatcher(bot=bot, storage=storage)
version = "4.0.0-beta(16)"
version = "4.0.0-beta(16-fixed)"
register_middleware(dp)
+4 -7
View File
@@ -542,8 +542,8 @@ async def update_balance(
amount: float,
session: Any = None,
is_admin: bool = False,
skip_referral: bool = False, # <- флаг "пропустить реферальное начисление"
skip_cashback: bool = False # <- флаг "пропустить кэшбэк"
skip_referral: bool = False, # <- флаг "пропустить реферальное начисление"
skip_cashback: bool = False, # <- флаг "пропустить кэшбэк"
):
"""
Обновляет баланс пользователя в базе данных.
@@ -557,17 +557,14 @@ async def update_balance(
session = conn
# Если пополнение не от админа и не сказали пропустить кэшбэк
if (CASHBACK > 0 and amount > 0 and not is_admin and not skip_cashback):
if CASHBACK > 0 and amount > 0 and not is_admin and not skip_cashback:
extra = amount * (CASHBACK / 100.0)
else:
extra = 0
total_amount = int(amount + extra)
current_balance = await session.fetchval(
"SELECT balance FROM connections WHERE tg_id = $1",
tg_id
) or 0
current_balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id) or 0
new_balance = current_balance + total_amount
+50
View File
@@ -32,6 +32,7 @@ class AdminServersEditor(StatesGroup):
waiting_for_inbound_id = State()
waiting_for_server_name = State()
waiting_for_subscription_url = State()
waiting_for_new_domain = State()
@router.callback_query(
@@ -439,3 +440,52 @@ async def handle_clusters_sync(
await callback_query.message.answer(
text=f"❌ Произошла ошибка при синхронизации: {e}", reply_markup=build_admin_back_kb("servers")
)
@router.callback_query(AdminPanelCallback.filter(F.action == "change_domain"), IsAdminFilter())
async def request_new_domain(callback_query: CallbackQuery, state: FSMContext):
"""Запрашивает у администратора новый домен."""
await state.set_state(AdminServersEditor.waiting_for_new_domain)
await callback_query.message.edit_text(
text="🌐 Введите новый домен (без https://):\nПример: pocomachodomen.ru",
)
@router.message(AdminServersEditor.waiting_for_new_domain)
async def process_new_domain(message: Message, state: FSMContext, session: asyncpg.Connection):
"""Обновляет домен в таблице keys."""
new_domain = message.text.strip()
logger.info(f"[DomainChange] Новый домен, введённый администратором: '{new_domain}'")
if not new_domain or " " in new_domain or not new_domain.replace(".", "").isalnum():
logger.warning("[DomainChange] Некорректный домен")
await message.answer(
"🚫 Некорректный домен! Введите домен без http:// и без пробелов.",
reply_markup=build_admin_back_kb("admin"),
)
return
new_domain_url = f"https://{new_domain}"
logger.info(f"[DomainChange] Новый домен с протоколом: '{new_domain_url}'")
query = """
UPDATE keys
SET key = regexp_replace(key, '^https://[^/]+', $1::TEXT)
WHERE key NOT LIKE $1 || '%'
"""
try:
await session.execute(query, new_domain_url)
logger.info("[DomainChange] Запрос на обновление домена выполнен успешно.")
except Exception as e:
logger.error(f"[DomainChange] Ошибка при выполнении запроса: {e}")
await message.answer(f"❌ Ошибка при обновлении домена: {e}", reply_markup=build_admin_back_kb("admin"))
return
try:
sample = await session.fetchrow("SELECT key FROM keys LIMIT 1")
logger.info(f"[DomainChange] Пример обновленной записи: {sample}")
except Exception as e:
logger.error(f"[DomainChange] Ошибка при выборке обновленной записи: {e}")
await message.answer(f"✅ Домен успешно изменен на {new_domain}!", reply_markup=build_admin_back_kb("admin"))
await state.clear()
+29 -18
View File
@@ -1,7 +1,8 @@
import asyncio
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import Any
import pytz
from aiogram import F, Router, types
from aiogram.exceptions import TelegramBadRequest
from aiogram.fsm.context import FSMContext
@@ -39,6 +40,8 @@ from keyboards.admin.users_kb import (
)
from logger import logger
MOSCOW_TZ = pytz.timezone("Europe/Moscow")
router = Router()
@@ -431,23 +434,31 @@ async def handle_expiry_time_input(message: Message, state: FSMContext, session:
)
return
if op_type == "add":
days = int(message.text)
text = f"✅ Ко времени действия ключа добавлено <b>{days} дн.</b>"
await change_expiry_time(key_details["expiry_time"] + days * 24 * 3600 * 1000, email, session)
elif op_type == "take":
days = int(message.text)
text = f"Из времени действия ключа вычтено <b>{days} дн.</b>"
await change_expiry_time(key_details["expiry_time"] - days * 24 * 3600 * 1000, email, session)
else:
try:
expiry_time = int(datetime.strptime(message.text, "%Y-%m-%d %H:%M").timestamp() * 1000)
text = f"Время действия ключа изменено на <b>{message.text}</b>"
await change_expiry_time(expiry_time, email, session)
except ValueError:
text = "🚫 Пожалуйста, используйте корректный формат даты!"
except Exception as e:
text = f"❗ Произошла ошибка во время изменения времени действия ключа: {e}"
try:
current_expiry_time = datetime.fromtimestamp(key_details["expiry_time"] / 1000, tz=MOSCOW_TZ)
if op_type == "add":
days = int(message.text)
new_expiry_time = current_expiry_time + timedelta(days=days)
text = f"Ко времени действия ключа добавлено <b>{days} дн.</b>"
elif op_type == "take":
days = int(message.text)
new_expiry_time = current_expiry_time - timedelta(days=days)
text = f"Из времени действия ключа вычтено <b>{days} дн.</b>"
else:
new_expiry_time = datetime.strptime(message.text, "%Y-%m-%d %H:%M")
new_expiry_time = MOSCOW_TZ.localize(new_expiry_time)
text = f"✅ Время действия ключа изменено на <b>{message.text} (МСК)</b>"
new_expiry_timestamp = int(new_expiry_time.timestamp() * 1000)
await change_expiry_time(new_expiry_timestamp, email, session)
except ValueError:
text = "🚫 Пожалуйста, используйте корректный формат даты (ГГГГ-ММ-ДД ЧЧ:ММ)!"
except Exception as e:
text = f"❗ Произошла ошибка во время изменения времени действия ключа: {e}"
await message.answer(text=text, reply_markup=build_users_key_show_kb(tg_id, email))
-5
View File
@@ -20,7 +20,6 @@ async def create_key_on_cluster(
servers = await get_servers()
cluster = servers.get(cluster_id)
# Если не нашли кластер по ключу, ищем сервер по имени (аналогично renew_key_in_cluster, delete_key_from_cluster)
if not cluster:
found_servers = []
for _key, server_list in servers.items():
@@ -213,7 +212,6 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
servers = await get_servers()
cluster = servers.get(cluster_id)
# Аналогичная логика поиска кластера или конкретного сервера
if not cluster:
found_servers = []
for _key, server_list in servers.items():
@@ -405,7 +403,6 @@ async def toggle_client_on_cluster(cluster_id: str, email: str, client_id: str,
cluster = servers.get(cluster_id)
if not cluster:
# Поиск по имени сервера, если не найден кластер
found_servers = []
for _, server_list in servers.items():
for server_info in server_list:
@@ -442,10 +439,8 @@ async def toggle_client_on_cluster(cluster_id: str, email: str, client_id: str,
tasks.append(toggle_client(xui, int(inbound_id), unique_email, client_id, enable))
# Выполняем все задачи параллельно
task_results = await asyncio.gather(*tasks, return_exceptions=True)
# Формируем результаты для каждого сервера
for server_info, result in zip(cluster, task_results, strict=False):
server_name = server_info.get("server_name", "unknown")
if isinstance(result, Exception):
+15 -35
View File
@@ -8,14 +8,10 @@ import aiohttp
import asyncpg
from aiohttp import web
from config import (
DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, SUPERNODE,
TRANSITION_DATE_STR, USE_COUNTRY_SELECTION
)
from config import DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, SUPERNODE, TRANSITION_DATE_STR, USE_COUNTRY_SELECTION
from database import get_key_details, get_servers
from logger import logger
db_pool = None
@@ -59,10 +55,7 @@ async def combine_unique_lines(urls, tg_id, query_string):
logger.info(f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}")
urls_with_query = [
f"{url}?{query_string}" if query_string else url
for url in urls
]
urls_with_query = [f"{url}?{query_string}" if query_string else url for url in urls]
logger.info(f"Составлены URL-адреса: {urls_with_query}")
tasks = [fetch_url_content(url, tg_id) for url in urls_with_query]
@@ -91,10 +84,7 @@ async def get_subscription_urls(server_id: str, email: str, conn) -> list:
"""
if USE_COUNTRY_SELECTION:
logger.info(f"Режим выбора страны активен. Ищем сервер {server_id} в БД.")
server_data = await conn.fetchrow(
"SELECT subscription_url FROM servers WHERE server_name = $1",
server_id
)
server_data = await conn.fetchrow("SELECT subscription_url FROM servers WHERE server_name = $1", server_id)
if not server_data:
logger.warning(f"Не найден сервер {server_id} в БД!")
return []
@@ -124,8 +114,7 @@ async def handle_subscription(request, old_subscription=False):
return web.Response(text="❌ Неверные параметры запроса.", status=400)
logger.info(
f"Обработка запроса для {'старого' if old_subscription else 'нового'} клиента: "
f"email={email}, tg_id={tg_id}"
f"Обработка запроса для {'старого' if old_subscription else 'нового'} клиента: email={email}, tg_id={tg_id}"
)
await init_db_pool()
@@ -152,29 +141,22 @@ async def handle_subscription(request, old_subscription=False):
if created_at_ms >= transition_timestamp_ms_adjusted:
logger.info(f"Клиент с email {email} является новым.")
return web.Response(
text="❌ Эта ссылка устарела. Пожалуйста, обновите ссылку.",
status=400
)
return web.Response(text="❌ Эта ссылка устарела. Пожалуйста, обновите ссылку.", status=400)
urls = await get_subscription_urls(server_id, email, conn)
if not urls:
return web.Response(text="❌ Сервер не найден.", status=404)
query_string = request.query_string if not old_subscription else ""
combined_subscriptions = await combine_unique_lines(
urls,
tg_id or email,
query_string
)
combined_subscriptions = await combine_unique_lines(urls, tg_id or email, query_string)
random.shuffle(combined_subscriptions)
time_left = None
for line in combined_subscriptions:
if '#' in line:
_, meta = line.split('#', 1)
parts = meta.split('-')
if "#" in line:
_, meta = line.split("#", 1)
parts = meta.split("-")
if len(parts) >= 2:
candidate = parts[-1]
if candidate:
@@ -185,10 +167,10 @@ async def handle_subscription(request, old_subscription=False):
cleaned_subscriptions = []
for line in combined_subscriptions:
if '#' in line:
base, meta = line.split('#', 1)
meta_clean = meta.split('-', 1)[0]
cleaned_line = base + '#' + meta_clean
if "#" in line:
base, meta = line.split("#", 1)
meta_clean = meta.split("-", 1)[0]
cleaned_line = base + "#" + meta_clean
else:
cleaned_line = line
cleaned_subscriptions.append(cleaned_line)
@@ -203,16 +185,14 @@ async def handle_subscription(request, old_subscription=False):
final_subscriptions = [profile_line] + cleaned_subscriptions
base64_encoded = base64.b64encode(
"\n".join(final_subscriptions).encode("utf-8")
).decode("utf-8")
base64_encoded = base64.b64encode("\n".join(final_subscriptions).encode("utf-8")).decode("utf-8")
encoded_project_name = f"{PROJECT_NAME} - {SUB_MESSAGE}"
headers = {
"Content-Type": "text/plain; charset=utf-8",
"Content-Disposition": "inline",
"profile-update-interval": "7",
"profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8")
"profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
}
logger.info(f"Возвращаем объединенные подписки для email: {email}")
@@ -134,14 +134,11 @@ async def notify_users_no_traffic(bot: Bot, conn: asyncpg.Connection, current_ti
client_id = key.get("client_id")
notified = key.get("notified")
logger.info(f"Обработка ключа для {email}: created_at = {created_at}")
if created_at is None:
logger.warning(f"Для {email} нет значения created_at. Пропускаем.")
continue
if notified is True:
logger.info(f"Уведомление для {email} уже отправлено, пропускаем.")
continue
created_at_dt = pytz.utc.localize(datetime.fromtimestamp(created_at / 1000)).astimezone(moscow_tz)
+3668 -4931
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -61,10 +61,10 @@ async def process_callback_view_profile(
target_message = callback_query_or_message
image_path = os.path.join("img", "profile.jpg")
logger.info(f"Переход в профиль. Используется изображение: {image_path}")
key_count = await get_key_count(chat_id)
balance = await get_balance(chat_id)
if balance is None:
balance = 0
balance = await get_balance(chat_id) or 0
conn = await asyncpg.connect(DATABASE_URL)
try:
@@ -100,6 +100,7 @@ async def process_callback_view_profile(
reply_markup=builder.as_markup(),
media_path=image_path,
disable_web_page_preview=False,
force_text=True,
)
finally:
await conn.close()
@@ -195,7 +196,7 @@ async def invite_handler(callback_query: CallbackQuery):
image_path = os.path.join("img", "pic_invite.jpg")
builder = InlineKeyboardBuilder()
builder.button(text="👥 Пригласить друга", switch_inline_query="invite ")
builder.button(text="👥 Пригласить друга", switch_inline_query="invite")
builder.button(text="👤 Личный кабинет", callback_data="profile")
builder.adjust(1)
+1 -1
View File
@@ -177,7 +177,7 @@ async def edit_or_send_message(
try:
await target_message.edit_media(media=media, reply_markup=reply_markup)
return
except Exception as e:
except Exception:
await target_message.answer_photo(
photo=BufferedInputFile(image_data, filename=os.path.basename(media_path)),
caption=text,
+1
View File
@@ -30,6 +30,7 @@ def build_management_kb() -> InlineKeyboardMarkup:
builder.button(text="💾 Создать резервную копию", callback_data=AdminPanelCallback(action="backups").pack())
builder.button(text="🚫 Заблокировавшие бота", callback_data=AdminPanelCallback(action="bans").pack())
builder.button(text="🔄 Перезагрузить бота", callback_data=AdminPanelCallback(action="restart").pack())
builder.button(text="🌐 Сменить домен", callback_data=AdminPanelCallback(action="change_domain").pack())
builder.row(build_admin_back_btn())
builder.adjust(1)
return builder.as_markup()
+1 -1
View File
File diff suppressed because one or more lines are too long