From 3592eccea9aa74a950ff4cfb60c08987ba1ced6e Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 09:21:10 +0300 Subject: [PATCH 01/14] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=BF=D1=81=D0=B8=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Убираем профиль - Отказ от SUB_MESSAGE - Перадача подпсики и времени работы вместо SUB_MESSAGE - Отдельная итеграция для Happ по хукам. --- handlers/keys/subscriptions.py | 71 +++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 58309805..75a416be 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -1,6 +1,7 @@ import asyncio import base64 import random +import re import urllib.parse from datetime import datetime @@ -8,7 +9,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, SUPPORT_CHAT_URL, USERNAME_BOT +) from database import get_key_details, get_servers from logger import logger @@ -54,7 +58,6 @@ async def combine_unique_lines(urls, tg_id, query_string): return await fetch_url_content(url_with_query, tg_id) 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] logger.info(f"Составлены URL-адреса: {urls_with_query}") @@ -72,7 +75,6 @@ async def combine_unique_lines(urls, tg_id, query_string): transition_date = datetime.strptime(TRANSITION_DATE_STR, "%Y-%m-%d %H:%M:%S") transition_timestamp_ms = int(transition_date.timestamp() * 1000) transition_timestamp_ms_adjusted = transition_timestamp_ms - (3 * 60 * 60 * 1000) - logger.info(f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}") @@ -84,7 +86,9 @@ 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 [] @@ -113,15 +117,11 @@ async def handle_subscription(request, old_subscription=False): logger.warning("Получен запрос с отсутствующими параметрами") return web.Response(text="❌ Неверные параметры запроса.", status=400) - logger.info( - f"Обработка запроса для {'старого' if old_subscription else 'нового'} клиента: email={email}, tg_id={tg_id}" - ) - + logger.info(f"Обработка запроса для {'старого' if old_subscription else 'нового'} клиента: email={email}, tg_id={tg_id}") await init_db_pool() async with db_pool.acquire() as conn: client_data = await get_key_details(email, conn) - if not client_data: logger.warning(f"Клиент с email {email} не найден в базе.") return web.Response(text="❌ Клиент с таким email не найден.", status=404) @@ -136,9 +136,7 @@ async def handle_subscription(request, old_subscription=False): if old_subscription: created_at_ms = client_data["created_at"] created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000) - logger.info(f"created_at для {email}: {created_at_datetime}, server_id: {server_id}") - if created_at_ms >= transition_timestamp_ms_adjusted: logger.info(f"Клиент с email {email} является новым.") return web.Response(text="❌ Эта ссылка устарела. Пожалуйста, обновите ссылку.", status=400) @@ -149,7 +147,6 @@ async def handle_subscription(request, old_subscription=False): query_string = request.query_string if not old_subscription else "" combined_subscriptions = await combine_unique_lines(urls, tg_id or email, query_string) - random.shuffle(combined_subscriptions) time_left = None @@ -157,10 +154,12 @@ async def handle_subscription(request, old_subscription=False): if "#" in line: _, meta = line.split("#", 1) parts = meta.split("-") - if len(parts) >= 2: + if len(parts) >= 3: candidate = parts[-1] - if candidate: - time_left = candidate + candidate_decoded = urllib.parse.unquote(candidate) + match = re.search(r'(\d+D,\d+H)', candidate_decoded) + if match: + time_left = match.group(1) + "⏳" break if not time_left: time_left = "N/A" @@ -175,25 +174,33 @@ async def handle_subscription(request, old_subscription=False): cleaned_line = line cleaned_subscriptions.append(cleaned_line) - profile_info = f"👤Профиль: {email} - {time_left}" - encoded_profile_info = urllib.parse.quote(profile_info) - - profile_line = ( - "vless://00000000-0000-0000-0000-000000000000@my.profile:443" - "?encryption=none&security=none#" + encoded_profile_info - ) - - final_subscriptions = [profile_line] + cleaned_subscriptions - + final_subscriptions = cleaned_subscriptions 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"), - } + subscription_info = f"📄 Подписка: {email} - {time_left}" + + user_agent = request.headers.get("User-Agent", "") + if "Happ" in user_agent: + encoded_project_name = f"{PROJECT_NAME}" + support_username = SUPPORT_CHAT_URL.split("https://t.me/")[-1] + announce_str = f"Поддержка @{support_username} 🔗(Справа)\n\nБот @{USERNAME_BOT} ⓘ(Слева)\n\n📄 Подписка: {email} - {time_left}" + headers = { + "Content-Type": "text/plain; charset=utf-8", + "Content-Disposition": "inline", + "profile-update-interval": "3", + "profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"), + "support-url": SUPPORT_CHAT_URL, + "announce": "base64:" + base64.b64encode(announce_str.encode("utf-8")).decode("utf-8"), + "profile-web-page-url": f"https://t.me/{USERNAME_BOT}" + } + else: + encoded_project_name = f"{PROJECT_NAME}\n{subscription_info}" + 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") + } logger.info(f"Возвращаем объединенные подписки для email: {email}") return web.Response(text=base64_encoded, headers=headers) From 903cde9a78ab98db49abb2e4481cad5c80c3cac0 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 10:19:05 +0300 Subject: [PATCH 02/14] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D1=87=D0=B0?= =?UTF-8?q?=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - фискс отображения когда остаются только часы --- handlers/keys/subscriptions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 75a416be..8128e2d6 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -157,9 +157,11 @@ async def handle_subscription(request, old_subscription=False): if len(parts) >= 3: candidate = parts[-1] candidate_decoded = urllib.parse.unquote(candidate) - match = re.search(r'(\d+D,\d+H)', candidate_decoded) + match = re.search(r'(?:(\d+D,))?(\d+H)', candidate_decoded) if match: - time_left = match.group(1) + "⏳" + day_part = match.group(1) or "" + hour_part = match.group(2) + time_left = (day_part + hour_part).strip() + " ⏳" break if not time_left: time_left = "N/A" @@ -183,7 +185,7 @@ async def handle_subscription(request, old_subscription=False): if "Happ" in user_agent: encoded_project_name = f"{PROJECT_NAME}" support_username = SUPPORT_CHAT_URL.split("https://t.me/")[-1] - announce_str = f"Поддержка @{support_username} 🔗(Справа)\n\nБот @{USERNAME_BOT} ⓘ(Слева)\n\n📄 Подписка: {email} - {time_left}" + announce_str = f"Поддержка @{support_username} 🔗(Справа)\n📄 Подписка: {email} - {time_left}\nБот @{USERNAME_BOT} ⓘ(Слева)" headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", From 19c4c0496ab8a6eca0580f179b91cabd4b31d720 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 10:30:18 +0300 Subject: [PATCH 03/14] =?UTF-8?q?=D0=95=D0=B4=D0=B8=D0=BD=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Единое время обновления подписки --- handlers/keys/subscriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 8128e2d6..e2bac8f4 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -200,7 +200,7 @@ async def handle_subscription(request, old_subscription=False): headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", - "profile-update-interval": "7", + "profile-update-interval": "3", "profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8") } From 2ba961515108236e5d7b4dfe4490b439138a9704 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 10:51:51 +0300 Subject: [PATCH 04/14] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Корректное отображения трафика --- handlers/keys/subscriptions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index e2bac8f4..fa765bf4 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -170,7 +170,12 @@ async def handle_subscription(request, old_subscription=False): for line in combined_subscriptions: if "#" in line: base, meta = line.split("#", 1) - meta_clean = meta.split("-", 1)[0] + parts = meta.split("-") + # Если получаем 4 части, берем первую (страна) и третью (трафик) + if len(parts) == 4: + meta_clean = parts[0] + "-" + parts[2] + else: + meta_clean = parts[0] cleaned_line = base + "#" + meta_clean else: cleaned_line = line From 773db02746ffbd7dfa0f2b4f16c684962ea0c3b4 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 11:17:33 +0300 Subject: [PATCH 05/14] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Фикс отображения данных для Happ --- handlers/keys/subscriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index fa765bf4..2650f838 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -190,7 +190,7 @@ async def handle_subscription(request, old_subscription=False): if "Happ" in user_agent: encoded_project_name = f"{PROJECT_NAME}" support_username = SUPPORT_CHAT_URL.split("https://t.me/")[-1] - announce_str = f"Поддержка @{support_username} 🔗(Справа)\n📄 Подписка: {email} - {time_left}\nБот @{USERNAME_BOT} ⓘ(Слева)" + announce_str = f"↖️Бот|📄 Подписка: {email} - {time_left}|Поддержка↗️" headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", From 31822ee63498c600000bd14f6a5090327e9aa062 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 11:19:52 +0300 Subject: [PATCH 06/14] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=82=D1=81=D1=82=D1=83=D0=BF?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/keys/subscriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 2650f838..524211cc 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -190,7 +190,7 @@ async def handle_subscription(request, old_subscription=False): if "Happ" in user_agent: encoded_project_name = f"{PROJECT_NAME}" support_username = SUPPORT_CHAT_URL.split("https://t.me/")[-1] - announce_str = f"↖️Бот|📄 Подписка: {email} - {time_left}|Поддержка↗️" + announce_str = f"↖️Бот | 📄 Подписка: {email} - {time_left} | Поддержка↗️" headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", From 40f1f1b14e5da161da2bc9767b066e406d44d998 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 12:12:34 +0300 Subject: [PATCH 07/14] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20Happ?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен хедер трафика и даты завершения --- handlers/keys/subscriptions.py | 48 ++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 524211cc..2835def6 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -2,6 +2,7 @@ import asyncio import base64 import random import re +import time import urllib.parse from datetime import datetime @@ -157,11 +158,11 @@ async def handle_subscription(request, old_subscription=False): if len(parts) >= 3: candidate = parts[-1] candidate_decoded = urllib.parse.unquote(candidate) - match = re.search(r'(?:(\d+D,))?(\d+H)', candidate_decoded) - if match: - day_part = match.group(1) or "" - hour_part = match.group(2) - time_left = (day_part + hour_part).strip() + " ⏳" + m = re.search(r'(?:(\d+)D,)?(\d+)H', candidate_decoded) + if m: + d = int(m.group(1)) if m.group(1) else 0 + h = int(m.group(2)) + time_left = f"{d}D,{h}H ⏳" if d else f"{h}H ⏳" break if not time_left: time_left = "N/A" @@ -171,7 +172,6 @@ async def handle_subscription(request, old_subscription=False): if "#" in line: base, meta = line.split("#", 1) parts = meta.split("-") - # Если получаем 4 части, берем первую (страна) и третью (трафик) if len(parts) == 4: meta_clean = parts[0] + "-" + parts[2] else: @@ -190,7 +190,38 @@ async def handle_subscription(request, old_subscription=False): if "Happ" in user_agent: encoded_project_name = f"{PROJECT_NAME}" support_username = SUPPORT_CHAT_URL.split("https://t.me/")[-1] - announce_str = f"↖️Бот | 📄 Подписка: {email} - {time_left} | Поддержка↗️" + announce_str = f"↖️Бот | {subscription_info} | Поддержка↗️" + + expire_timestamp = 0 + m = re.search(r'(?:(\d+)D,)?(\d+)H', time_left) + if m: + d = int(m.group(1)) if m.group(1) else 0 + h = int(m.group(2)) + expire_timestamp = int(time.time() + d * 86400 + h * 3600) + + total_traffic_bytes = 0 + if cleaned_subscriptions: + first_line = cleaned_subscriptions[0] + if "#" in first_line: + _, meta_clean = first_line.split("#", 1) + parts = meta_clean.split("-") + if len(parts) >= 2: + total_str = urllib.parse.unquote(parts[1]) + m_total = re.search(r'([\d.]+)([GMK]B)', total_str) + if m_total: + value = float(m_total.group(1)) + unit = m_total.group(2) + if unit == "GB": + total_traffic_bytes = int(value * 1073741824) + elif unit == "MB": + total_traffic_bytes = int(value * 1048576) + elif unit == "KB": + total_traffic_bytes = int(value * 1024) + else: + total_traffic_bytes = int(value) + + subscription_userinfo = f"upload=0; download=0; total={total_traffic_bytes}; expire={expire_timestamp}" + headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", @@ -198,7 +229,8 @@ async def handle_subscription(request, old_subscription=False): "profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"), "support-url": SUPPORT_CHAT_URL, "announce": "base64:" + base64.b64encode(announce_str.encode("utf-8")).decode("utf-8"), - "profile-web-page-url": f"https://t.me/{USERNAME_BOT}" + "profile-web-page-url": f"https://t.me/{USERNAME_BOT}", + "subscription-userinfo": subscription_userinfo } else: encoded_project_name = f"{PROJECT_NAME}\n{subscription_info}" From 29f6fe40e0cf3d61f88588fdae9070dce3ba85d9 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 13:30:46 +0300 Subject: [PATCH 08/14] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Исправлено отображение с трафиком и без для спуперноды вкл/выкл - Исправлен парсинг в общем для разных режимов. --- handlers/keys/subscriptions.py | 66 +++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 2835def6..4e9daa40 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -82,8 +82,7 @@ logger.info(f"Время перехода (с поправкой на часов async def get_subscription_urls(server_id: str, email: str, conn) -> list: """ Универсальная функция, которая в зависимости от флага USE_COUNTRY_SELECTION - получает список URL-адресов для подписки. Возвращает пустой список, если - нужные данные не найдены. + получает список URL-адресов для подписки. Возвращает пустой список, если нужные данные не найдены. """ if USE_COUNTRY_SELECTION: logger.info(f"Режим выбора страны активен. Ищем сервер {server_id} в БД.") @@ -153,29 +152,56 @@ async def handle_subscription(request, old_subscription=False): time_left = None for line in combined_subscriptions: if "#" in line: - _, meta = line.split("#", 1) + try: + _, meta = line.split("#", 1) + except ValueError: + continue parts = meta.split("-") - if len(parts) >= 3: - candidate = parts[-1] - candidate_decoded = urllib.parse.unquote(candidate) - m = re.search(r'(?:(\d+)D,)?(\d+)H', candidate_decoded) - if m: - d = int(m.group(1)) if m.group(1) else 0 - h = int(m.group(2)) - time_left = f"{d}D,{h}H ⏳" if d else f"{h}H ⏳" - break + + if SUPERNODE: + candidate = parts[-1] if parts else "" + else: + candidate = parts[-1] if parts else "" + candidate_decoded = urllib.parse.unquote(candidate) + m = re.search(r'(?:(\d+)D,)?(\d+)H', candidate_decoded) + if m: + d = int(m.group(1)) if m.group(1) else 0 + h = int(m.group(2)) + time_left = f"{d}D,{h}H ⏳" if d else f"{h}H ⏳" + break if not time_left: time_left = "N/A" cleaned_subscriptions = [] for line in combined_subscriptions: if "#" in line: - base, meta = line.split("#", 1) + try: + base, meta = line.split("#", 1) + except ValueError: + continue parts = meta.split("-") - if len(parts) == 4: - meta_clean = parts[0] + "-" + parts[2] + if SUPERNODE: + if parts: + country = parts[0] + if "_" in country: + country = country.split("_", 1)[1] + if len(parts) == 4: + meta_clean = country + "-" + parts[2] + elif len(parts) == 3: + meta_clean = country + else: + meta_clean = country + else: + meta_clean = "" else: - meta_clean = parts[0] + if len(parts) == 4: + meta_clean = parts[0] + "-" + parts[2] + elif len(parts) == 3: + meta_clean = parts[0] + "-" + parts[1] + elif parts: + meta_clean = parts[0] + else: + meta_clean = "" cleaned_line = base + "#" + meta_clean else: cleaned_line = line @@ -193,10 +219,10 @@ async def handle_subscription(request, old_subscription=False): announce_str = f"↖️Бот | {subscription_info} | Поддержка↗️" expire_timestamp = 0 - m = re.search(r'(?:(\d+)D,)?(\d+)H', time_left) - if m: - d = int(m.group(1)) if m.group(1) else 0 - h = int(m.group(2)) + m_expire = re.search(r'(?:(\d+)D,)?(\d+)H', time_left) + if m_expire: + d = int(m_expire.group(1)) if m_expire.group(1) else 0 + h = int(m_expire.group(2)) expire_timestamp = int(time.time() + d * 86400 + h * 3600) total_traffic_bytes = 0 From 316bd191a2f31eb7ce1596ad828972bdffb971fe Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 13:45:59 +0300 Subject: [PATCH 09/14] =?UTF-8?q?=D0=A3=D0=BA=D0=B0=D0=B7=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=B5=D0=BD=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/keys/subscriptions.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 4e9daa40..9fe44941 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -149,6 +149,7 @@ async def handle_subscription(request, old_subscription=False): combined_subscriptions = await combine_unique_lines(urls, tg_id or email, query_string) random.shuffle(combined_subscriptions) + # Извлечение времени (time_left) из метаданных подписки. time_left = None for line in combined_subscriptions: if "#" in line: @@ -157,12 +158,9 @@ async def handle_subscription(request, old_subscription=False): except ValueError: continue parts = meta.split("-") - - if SUPERNODE: - candidate = parts[-1] if parts else "" - else: - candidate = parts[-1] if parts else "" + candidate = parts[-1] if parts else "" candidate_decoded = urllib.parse.unquote(candidate) + # Ищем формат "11D,1H", либо "1H" m = re.search(r'(?:(\d+)D,)?(\d+)H', candidate_decoded) if m: d = int(m.group(1)) if m.group(1) else 0 @@ -172,6 +170,7 @@ async def handle_subscription(request, old_subscription=False): if not time_left: time_left = "N/A" + # Логика зависит от значения SUPERNODE. cleaned_subscriptions = [] for line in combined_subscriptions: if "#" in line: @@ -181,19 +180,24 @@ async def handle_subscription(request, old_subscription=False): continue parts = meta.split("-") if SUPERNODE: + # Если SUPERNODE=True: + # Если трафик включен (4 поля или 3 поля): берем страну и трафик (если 4 поля – берем третью, если 3 – оставляем только страну) if parts: country = parts[0] + # Убираем префикс до символа "_" if "_" in country: country = country.split("_", 1)[1] if len(parts) == 4: meta_clean = country + "-" + parts[2] elif len(parts) == 3: + # Если трафик отключен, оставляем только страну meta_clean = country else: meta_clean = country else: meta_clean = "" else: + # Для SUPERNODE=False: if len(parts) == 4: meta_clean = parts[0] + "-" + parts[2] elif len(parts) == 3: @@ -210,14 +214,17 @@ async def handle_subscription(request, old_subscription=False): final_subscriptions = cleaned_subscriptions base64_encoded = base64.b64encode("\n".join(final_subscriptions).encode("utf-8")).decode("utf-8") + # Информации о подписке, которая используется в заголовке вместе с PROJECT_NAME subscription_info = f"📄 Подписка: {email} - {time_left}" user_agent = request.headers.get("User-Agent", "") if "Happ" in user_agent: + # Для Happ передаётся только PROJECT_NAME encoded_project_name = f"{PROJECT_NAME}" support_username = SUPPORT_CHAT_URL.split("https://t.me/")[-1] announce_str = f"↖️Бот | {subscription_info} | Поддержка↗️" + # Вычисляем expire таймштамп + текущее время expire_timestamp = 0 m_expire = re.search(r'(?:(\d+)D,)?(\d+)H', time_left) if m_expire: @@ -225,6 +232,7 @@ async def handle_subscription(request, old_subscription=False): h = int(m_expire.group(2)) expire_timestamp = int(time.time() + d * 86400 + h * 3600) + # Извлекаем общий трафик (total) total_traffic_bytes = 0 if cleaned_subscriptions: first_line = cleaned_subscriptions[0] @@ -246,6 +254,7 @@ async def handle_subscription(request, old_subscription=False): else: total_traffic_bytes = int(value) + # Заголовок subscription-userinfo для Happ subscription_userinfo = f"upload=0; download=0; total={total_traffic_bytes}; expire={expire_timestamp}" headers = { @@ -270,7 +279,6 @@ async def handle_subscription(request, old_subscription=False): logger.info(f"Возвращаем объединенные подписки для email: {email}") return web.Response(text=base64_encoded, headers=headers) - async def handle_old_subscription(request): """Обработка запроса для старых клиентов.""" return await handle_subscription(request, old_subscription=True) From 8b159ab75dedda83eb3ff3f9277c69fcd54a4706 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 14:31:43 +0300 Subject: [PATCH 10/14] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D0=B0?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D0=BD=D0=B3=D0=B0=20=D0=B2=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - исправлен парсинг времени если передается без запятой --- handlers/keys/subscriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 9fe44941..f267c623 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -161,7 +161,7 @@ async def handle_subscription(request, old_subscription=False): candidate = parts[-1] if parts else "" candidate_decoded = urllib.parse.unquote(candidate) # Ищем формат "11D,1H", либо "1H" - m = re.search(r'(?:(\d+)D,)?(\d+)H', candidate_decoded) + m = re.search(r'(?:(\d+)D,?)?(\d+)H', candidate_decoded) if m: d = int(m.group(1)) if m.group(1) else 0 h = int(m.group(2)) From e01a7efe4aaae1a4c1e054da9101e57688365c65 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 17:35:40 +0300 Subject: [PATCH 11/14] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D0=BF=D0=B8=D1=81=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Поправлено время для SUPERNODA = False - Добавлен подсчет использованного трафика для Happ --- handlers/keys/subscriptions.py | 66 ++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index f267c623..c2f3a09e 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -12,14 +12,13 @@ from aiohttp import web from config import ( DATABASE_URL, PROJECT_NAME, SUB_MESSAGE, SUPERNODE, - TRANSITION_DATE_STR, USE_COUNTRY_SELECTION, SUPPORT_CHAT_URL, USERNAME_BOT + TRANSITION_DATE_STR, USE_COUNTRY_SELECTION, SUPPORT_CHAT_URL, USERNAME_BOT, TOTAL_GB ) from database import get_key_details, get_servers from logger import logger db_pool = None - async def init_db_pool(): """Инициализация пула соединений, если он ещё не создан.""" global db_pool @@ -198,10 +197,15 @@ async def handle_subscription(request, old_subscription=False): meta_clean = "" else: # Для SUPERNODE=False: - if len(parts) == 4: + if len(parts) >= 4: meta_clean = parts[0] + "-" + parts[2] elif len(parts) == 3: - meta_clean = parts[0] + "-" + parts[1] + if re.search(r'\d+[DH]', parts[1], re.IGNORECASE): + meta_clean = parts[0] + else: + meta_clean = parts[0] + "-" + parts[1] + elif len(parts) == 2: + meta_clean = parts[0] elif parts: meta_clean = parts[0] else: @@ -233,29 +237,53 @@ async def handle_subscription(request, old_subscription=False): expire_timestamp = int(time.time() + d * 86400 + h * 3600) # Извлекаем общий трафик (total) - total_traffic_bytes = 0 - if cleaned_subscriptions: - first_line = cleaned_subscriptions[0] - if "#" in first_line: - _, meta_clean = first_line.split("#", 1) - parts = meta_clean.split("-") - if len(parts) >= 2: - total_str = urllib.parse.unquote(parts[1]) - m_total = re.search(r'([\d.]+)([GMK]B)', total_str) + if TOTAL_GB != 0: + # Извлекаем остаток трафика с каждой страны + расчет общего трафика. + country_remaining = {} + for line in combined_subscriptions: + if "#" not in line: + continue + try: + _, meta = line.split("#", 1) + except ValueError: + continue + parts = meta.split("-") + if len(parts) == 4: + remaining_str = parts[2] + elif len(parts) == 3: + remaining_str = parts[1] + else: + remaining_str = "" + if remaining_str: + remaining_str = urllib.parse.unquote(remaining_str) + remaining_str = remaining_str.replace(',', '.') + remaining_str = re.sub(r'[^0-9\.GMKB]', '', remaining_str) + m_total = re.search(r'([\d\.]+)([GMK]B)', remaining_str, re.IGNORECASE) if m_total: value = float(m_total.group(1)) - unit = m_total.group(2) + unit = m_total.group(2).upper() if unit == "GB": - total_traffic_bytes = int(value * 1073741824) + remaining_bytes = int(value * 1073741824) elif unit == "MB": - total_traffic_bytes = int(value * 1048576) + remaining_bytes = int(value * 1048576) elif unit == "KB": - total_traffic_bytes = int(value * 1024) + remaining_bytes = int(value * 1024) else: - total_traffic_bytes = int(value) + remaining_bytes = int(value) + country = parts[0].strip() + country_remaining[country] = remaining_bytes + num_countries = len(country_remaining) + issued_per_country = int(TOTAL_GB * 1073741824) + total_traffic_bytes = issued_per_country * num_countries + consumed_traffic_bytes = total_traffic_bytes - sum(country_remaining.values()) + if consumed_traffic_bytes < 0: + consumed_traffic_bytes = 0 + else: + consumed_traffic_bytes = 0 + total_traffic_bytes = 0 # Заголовок subscription-userinfo для Happ - subscription_userinfo = f"upload=0; download=0; total={total_traffic_bytes}; expire={expire_timestamp}" + subscription_userinfo = f"upload=0; download={consumed_traffic_bytes}; total={total_traffic_bytes}; expire={expire_timestamp}" headers = { "Content-Type": "text/plain; charset=utf-8", From bd39825cf93c8e49723db8973a4e12753bfe3809 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 19:40:47 +0300 Subject: [PATCH 12/14] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=87=D0=B5=D1=82=D0=B0=20=D0=BE=D1=81=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Не посмотрел что TOTAL_GB указывается в байтах --- handlers/keys/subscriptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index c2f3a09e..6d640a17 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -273,7 +273,7 @@ async def handle_subscription(request, old_subscription=False): country = parts[0].strip() country_remaining[country] = remaining_bytes num_countries = len(country_remaining) - issued_per_country = int(TOTAL_GB * 1073741824) + issued_per_country = TOTAL_GB total_traffic_bytes = issued_per_country * num_countries consumed_traffic_bytes = total_traffic_bytes - sum(country_remaining.values()) if consumed_traffic_bytes < 0: From 4c977d07ce1a90c29d67f87eae5c04e167837f87 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Tue, 18 Feb 2025 23:57:38 +0300 Subject: [PATCH 13/14] =?UTF-8?q?=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Почитсил коментарии. --- handlers/keys/subscriptions.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 6d640a17..eb1ef33a 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -148,7 +148,6 @@ async def handle_subscription(request, old_subscription=False): combined_subscriptions = await combine_unique_lines(urls, tg_id or email, query_string) random.shuffle(combined_subscriptions) - # Извлечение времени (time_left) из метаданных подписки. time_left = None for line in combined_subscriptions: if "#" in line: @@ -169,7 +168,6 @@ async def handle_subscription(request, old_subscription=False): if not time_left: time_left = "N/A" - # Логика зависит от значения SUPERNODE. cleaned_subscriptions = [] for line in combined_subscriptions: if "#" in line: @@ -179,17 +177,13 @@ async def handle_subscription(request, old_subscription=False): continue parts = meta.split("-") if SUPERNODE: - # Если SUPERNODE=True: - # Если трафик включен (4 поля или 3 поля): берем страну и трафик (если 4 поля – берем третью, если 3 – оставляем только страну) if parts: country = parts[0] - # Убираем префикс до символа "_" if "_" in country: country = country.split("_", 1)[1] if len(parts) == 4: meta_clean = country + "-" + parts[2] elif len(parts) == 3: - # Если трафик отключен, оставляем только страну meta_clean = country else: meta_clean = country @@ -217,18 +211,14 @@ async def handle_subscription(request, old_subscription=False): final_subscriptions = cleaned_subscriptions base64_encoded = base64.b64encode("\n".join(final_subscriptions).encode("utf-8")).decode("utf-8") - - # Информации о подписке, которая используется в заголовке вместе с PROJECT_NAME subscription_info = f"📄 Подписка: {email} - {time_left}" user_agent = request.headers.get("User-Agent", "") if "Happ" in user_agent: - # Для Happ передаётся только PROJECT_NAME encoded_project_name = f"{PROJECT_NAME}" support_username = SUPPORT_CHAT_URL.split("https://t.me/")[-1] announce_str = f"↖️Бот | {subscription_info} | Поддержка↗️" - # Вычисляем expire таймштамп + текущее время expire_timestamp = 0 m_expire = re.search(r'(?:(\d+)D,)?(\d+)H', time_left) if m_expire: @@ -236,9 +226,7 @@ async def handle_subscription(request, old_subscription=False): h = int(m_expire.group(2)) expire_timestamp = int(time.time() + d * 86400 + h * 3600) - # Извлекаем общий трафик (total) if TOTAL_GB != 0: - # Извлекаем остаток трафика с каждой страны + расчет общего трафика. country_remaining = {} for line in combined_subscriptions: if "#" not in line: @@ -281,8 +269,7 @@ async def handle_subscription(request, old_subscription=False): else: consumed_traffic_bytes = 0 total_traffic_bytes = 0 - - # Заголовок subscription-userinfo для Happ + subscription_userinfo = f"upload=0; download={consumed_traffic_bytes}; total={total_traffic_bytes}; expire={expire_timestamp}" headers = { From b05e57fb1654aca2682dbd80e3f2fd193917a3b7 Mon Sep 17 00:00:00 2001 From: Capybara-z Date: Wed, 19 Feb 2025 01:31:15 +0300 Subject: [PATCH 14/14] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D0=B0?= =?UTF-8?q?=D1=80=D1=81=D0=B5=D1=80=D0=B0=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - фикс парсера времени для старых панелей --- handlers/keys/subscriptions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index eb1ef33a..b3fef841 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -156,10 +156,13 @@ async def handle_subscription(request, old_subscription=False): except ValueError: continue parts = meta.split("-") - candidate = parts[-1] if parts else "" + candidate = parts[-1].strip() if parts else "" candidate_decoded = urllib.parse.unquote(candidate) - # Ищем формат "11D,1H", либо "1H" - m = re.search(r'(?:(\d+)D,?)?(\d+)H', candidate_decoded) + m = re.search( + r'(?:(\d+)\s*[Dd]\s*,?\s*)?(\d+)\s*[Hh][^\d]*', + candidate_decoded, + re.IGNORECASE + ) if m: d = int(m.group(1)) if m.group(1) else 0 h = int(m.group(2))