add_change_domain/bug_fixes_16
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+5
-4
@@ -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
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user