Refactoring and fix
This commit is contained in:
+50
-13
@@ -1,14 +1,51 @@
|
|||||||
/venv
|
# Byte-compiled / optimized / DLL files
|
||||||
/__pycache__
|
__pycache__/
|
||||||
/vpn_users.db
|
*.py[cod]
|
||||||
/config.py
|
*$py.class
|
||||||
/database.db
|
|
||||||
/bot_old.py
|
# Distribution / packaging
|
||||||
/bot_old_2.py
|
dist/
|
||||||
/database.db
|
build/
|
||||||
/backup_pg.sh
|
*.egg-info/
|
||||||
/config copy.py
|
|
||||||
/docker-compose.yml
|
# Virtual environments
|
||||||
__pycache__
|
venv/
|
||||||
/handlers/texts.py
|
env/
|
||||||
|
.env/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Logs and databases
|
||||||
|
*.log
|
||||||
|
*.sqlite3
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Sensitive configuration files
|
||||||
|
config.py
|
||||||
|
config.ini
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Specific project files
|
||||||
|
vpn_users.db
|
||||||
|
database.db
|
||||||
|
bot_old.py
|
||||||
|
bot_old_2.py
|
||||||
|
backup_pg.sh
|
||||||
|
docker-compose.yml
|
||||||
|
config copy.py
|
||||||
|
handlers/texts.py
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
|||||||
@@ -6,32 +6,34 @@ from config import SERVERS
|
|||||||
|
|
||||||
session = None
|
session = None
|
||||||
|
|
||||||
|
|
||||||
async def login_with_credentials(server_id: str, username: str, password: str):
|
async def login_with_credentials(server_id: str, username: str, password: str):
|
||||||
global session
|
global session
|
||||||
session = aiohttp.ClientSession()
|
session = aiohttp.ClientSession()
|
||||||
api_url = SERVERS[server_id]['API_URL']
|
api_url = SERVERS[server_id]["API_URL"]
|
||||||
auth_url = f"{api_url}/login/"
|
auth_url = f"{api_url}/login/"
|
||||||
|
|
||||||
data = {
|
data = {"username": username, "password": password}
|
||||||
"username": username,
|
|
||||||
"password": password
|
|
||||||
}
|
|
||||||
|
|
||||||
async with session.post(auth_url, json=data) as response:
|
async with session.post(auth_url, json=data) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
session.cookie_jar.update_cookies(response.cookies)
|
session.cookie_jar.update_cookies(response.cookies)
|
||||||
return session
|
return session
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Ошибка авторизации: {response.status}, {await response.text()}")
|
raise Exception(
|
||||||
|
f"Ошибка авторизации: {response.status}, {await response.text()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_clients(session, server_id):
|
async def get_clients(session, server_id):
|
||||||
api_url = SERVERS[server_id]['API_URL']
|
api_url = SERVERS[server_id]["API_URL"]
|
||||||
async with session.get(f'{api_url}/panel/api/inbounds/list/') as response:
|
async with session.get(f"{api_url}/panel/api/inbounds/list/") as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
return await response.json()
|
return await response.json()
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Ошибка при получении клиентов: {response.status}, {await response.text()}")
|
raise Exception(
|
||||||
|
f"Ошибка при получении клиентов: {response.status}, {await response.text()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def link(session, server_id: str, client_id: str, email: str):
|
async def link(session, server_id: str, client_id: str, email: str):
|
||||||
@@ -42,29 +44,30 @@ async def link(session, server_id: str, client_id: str, email: str):
|
|||||||
:param email: str - электронная почта клиента
|
:param email: str - электронная почта клиента
|
||||||
:return: str - ссылка для подключения
|
:return: str - ссылка для подключения
|
||||||
"""
|
"""
|
||||||
response = await get_clients(session, server_id)
|
response = await get_clients(session, server_id)
|
||||||
|
|
||||||
if 'obj' not in response or len(response['obj']) == 0:
|
if "obj" not in response or len(response["obj"]) == 0:
|
||||||
raise Exception("Не удалось получить данные клиентов.")
|
raise Exception("Не удалось получить данные клиентов.")
|
||||||
|
|
||||||
inbounds = response['obj'][0]
|
inbounds = response["obj"][0]
|
||||||
settings = json.loads(inbounds['settings'])
|
settings = json.loads(inbounds["settings"])
|
||||||
|
|
||||||
stream_settings = json.loads(inbounds['streamSettings'])
|
stream_settings = json.loads(inbounds["streamSettings"])
|
||||||
tcp = stream_settings.get('network', 'tcp')
|
tcp = stream_settings.get("network", "tcp")
|
||||||
reality = stream_settings.get('security', 'reality')
|
reality = stream_settings.get("security", "reality")
|
||||||
flow = stream_settings.get('flow', 'xtls-rprx-vision')
|
flow = stream_settings.get("flow", "xtls-rprx-vision")
|
||||||
|
|
||||||
val = (
|
val = (
|
||||||
f"vless://{client_id}@{SERVERS[server_id]['DOMEN']}?type={tcp}&security={reality}&pbk={SERVERS[server_id]['PBK']}"
|
f"vless://{client_id}@{SERVERS[server_id]['DOMEN']}?type={tcp}&security={reality}&pbk={SERVERS[server_id]['PBK']}"
|
||||||
f"&fp=chrome&sni={SERVERS[server_id]['SNI']}&sid={SERVERS[server_id]['SID']}=%2F&flow={flow}#{SERVERS[server_id]['PREFIX']}-{email}"
|
f"&fp=chrome&sni={SERVERS[server_id]['SNI']}&sid={SERVERS[server_id]['SID']}=%2F&flow={flow}#{SERVERS[server_id]['PREFIX']}-{email}"
|
||||||
)
|
)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
async def link_subscription(email, server_id):
|
async def link_subscription(email, server_id):
|
||||||
server = SERVERS.get(server_id)
|
server = SERVERS.get(server_id)
|
||||||
if server:
|
if server:
|
||||||
subscription_url = f"{server['SUBSCRIPTION']}/{email}"
|
subscription_url = f"{server['SUBSCRIPTION']}/{email}"
|
||||||
return subscription_url
|
return subscription_url
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Server '{server_id}' not found in configuration.")
|
raise ValueError(f"Server '{server_id}' not found in configuration.")
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ async def backup_database():
|
|||||||
DATE = datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
DATE = datetime.now().strftime("%Y-%m-%d-%H%M%S")
|
||||||
BACKUP_FILE = f"{BACKUP_DIR}/{DB_NAME}-backup-{DATE}.sql"
|
BACKUP_FILE = f"{BACKUP_DIR}/{DB_NAME}-backup-{DATE}.sql"
|
||||||
|
|
||||||
os.environ['PGPASSWORD'] = DB_PASSWORD
|
os.environ["PGPASSWORD"] = DB_PASSWORD
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
['pg_dump', '-U', USER, '-h', HOST, '-F', 'c', '-f', BACKUP_FILE, DB_NAME],
|
["pg_dump", "-U", USER, "-h", HOST, "-F", "c", "-f", BACKUP_FILE, DB_NAME],
|
||||||
check=True
|
check=True,
|
||||||
)
|
)
|
||||||
logging.info(f"Бэкап базы данных создан: {BACKUP_FILE}")
|
logging.info(f"Бэкап базы данных создан: {BACKUP_FILE}")
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
@@ -30,8 +30,10 @@ async def backup_database():
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(BACKUP_FILE, 'rb') as backup_file:
|
with open(BACKUP_FILE, "rb") as backup_file:
|
||||||
backup_input_file = BufferedInputFile(backup_file.read(), filename=os.path.basename(BACKUP_FILE))
|
backup_input_file = BufferedInputFile(
|
||||||
|
backup_file.read(), filename=os.path.basename(BACKUP_FILE)
|
||||||
|
)
|
||||||
await bot.send_document(ADMIN_ID, backup_input_file)
|
await bot.send_document(ADMIN_ID, backup_input_file)
|
||||||
logging.info(f"Бэкап базы данных отправлен админу: {ADMIN_ID}")
|
logging.info(f"Бэкап базы данных отправлен админу: {ADMIN_ID}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -39,11 +41,24 @@ async def backup_database():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
['find', BACKUP_DIR, '-type', 'f', '-name', '*.sql', '-mtime', '+7', '-exec', 'rm', '{}', ';'],
|
[
|
||||||
check=True
|
"find",
|
||||||
|
BACKUP_DIR,
|
||||||
|
"-type",
|
||||||
|
"f",
|
||||||
|
"-name",
|
||||||
|
"*.sql",
|
||||||
|
"-mtime",
|
||||||
|
"+7",
|
||||||
|
"-exec",
|
||||||
|
"rm",
|
||||||
|
"{}",
|
||||||
|
";",
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
)
|
)
|
||||||
logging.info("Старые бэкапы удалены.")
|
logging.info("Старые бэкапы удалены.")
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
logging.error(f"Ошибка при удалении старых бэкапов: {e}")
|
logging.error(f"Ошибка при удалении старых бэкапов: {e}")
|
||||||
|
|
||||||
del os.environ['PGPASSWORD']
|
del os.environ["PGPASSWORD"]
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ router = Router()
|
|||||||
from handlers import commands, notifications, profile, start
|
from handlers import commands, notifications, profile, start
|
||||||
from handlers.admin import admin, admin_panel, user_editor
|
from handlers.admin import admin, admin_panel, user_editor
|
||||||
from handlers.keys import key_management, keys
|
from handlers.keys import key_management, keys
|
||||||
from handlers.payment import freekassa, pay
|
from handlers.payment import freekassa_pay, yookassa_pay
|
||||||
|
from middlewares.admin import AdminMiddleware
|
||||||
|
|
||||||
dp.include_router(admin.router)
|
dp.include_router(admin.router)
|
||||||
dp.include_router(admin_panel.router)
|
dp.include_router(admin_panel.router)
|
||||||
@@ -21,6 +22,9 @@ dp.include_router(start.router)
|
|||||||
dp.include_router(profile.router)
|
dp.include_router(profile.router)
|
||||||
dp.include_router(keys.router)
|
dp.include_router(keys.router)
|
||||||
dp.include_router(key_management.router)
|
dp.include_router(key_management.router)
|
||||||
dp.include_router(pay.router)
|
dp.include_router(yookassa_pay.router)
|
||||||
dp.include_router(freekassa.router)
|
dp.include_router(freekassa_pay.router)
|
||||||
dp.include_router(notifications.router)
|
dp.include_router(notifications.router)
|
||||||
|
|
||||||
|
dp.message.middleware(AdminMiddleware())
|
||||||
|
dp.callback_query.middleware(AdminMiddleware())
|
||||||
|
|||||||
@@ -1,14 +1,28 @@
|
|||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
from config import SERVERS
|
from config import SERVERS
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
async def add_client(
|
||||||
|
session,
|
||||||
|
server_id: str,
|
||||||
|
client_id: str,
|
||||||
|
email: str,
|
||||||
|
tg_id: str,
|
||||||
|
limit_ip: int,
|
||||||
|
total_gb: int,
|
||||||
|
expiry_time: int,
|
||||||
|
enable: bool,
|
||||||
|
flow: str,
|
||||||
|
):
|
||||||
|
api_url = SERVERS[server_id]["API_URL"]
|
||||||
|
url = f"{api_url}/panel/api/inbounds/addClient"
|
||||||
|
|
||||||
async def add_client(session, server_id: str, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str):
|
|
||||||
api_url = SERVERS[server_id]['API_URL']
|
|
||||||
url = f'{api_url}/panel/api/inbounds/addClient'
|
|
||||||
|
|
||||||
email = email.lower()
|
email = email.lower()
|
||||||
|
|
||||||
client_data = {
|
client_data = {
|
||||||
"id": client_id,
|
"id": client_id,
|
||||||
"alterId": 0,
|
"alterId": 0,
|
||||||
@@ -21,53 +35,58 @@ async def add_client(session, server_id: str, client_id: str, email: str, tg_id:
|
|||||||
"subId": email,
|
"subId": email,
|
||||||
"flow": flow,
|
"flow": flow,
|
||||||
}
|
}
|
||||||
|
|
||||||
settings = json.dumps({"clients": [client_data]})
|
settings = json.dumps({"clients": [client_data]})
|
||||||
|
|
||||||
data = {
|
data = {"id": 1, "settings": settings}
|
||||||
"id": 1,
|
|
||||||
"settings": settings
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
async with session.post(url, json=data, headers=headers) as response:
|
async with session.post(url, json=data, headers=headers) as response:
|
||||||
print(f"Запрос на добавление клиента: {data}")
|
logging.info(f"Запрос на добавление клиента: {data}")
|
||||||
print(f"Статус ответа: {response.status}")
|
logging.info(f"Статус ответа: {response.status}")
|
||||||
response_text = await response.text()
|
response_text = await response.text()
|
||||||
print(f"Ответ от сервера: {response_text}")
|
logging.info(f"Ответ от сервера: {response_text}")
|
||||||
|
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
print(f"Клиент добавлен: email={email}")
|
logging.info(f"Клиент добавлен: email={email}")
|
||||||
return await response.json()
|
return await response.json()
|
||||||
else:
|
else:
|
||||||
print(f"Ошибка при добавлении клиента: {response.status}, {response_text}")
|
logging.error(
|
||||||
|
f"Ошибка при добавлении клиента: {response.status}, {response_text}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def extend_client_key(session, server_id: str, tg_id, client_id, email: str, new_expiry_time: int) -> bool:
|
async def extend_client_key(
|
||||||
api_url = SERVERS[server_id]['API_URL']
|
session, server_id: str, tg_id, client_id, email: str, new_expiry_time: int
|
||||||
|
) -> bool:
|
||||||
async with session.get(f"{api_url}/panel/api/inbounds/getClientTraffics/{email}") as response:
|
api_url = SERVERS[server_id]["API_URL"]
|
||||||
print(f"GET {response.url} Status: {response.status}")
|
|
||||||
|
async with session.get(
|
||||||
|
f"{api_url}/panel/api/inbounds/getClientTraffics/{email}"
|
||||||
|
) as response:
|
||||||
|
logging.info(f"GET {response.url} Status: {response.status}")
|
||||||
response_text = await response.text()
|
response_text = await response.text()
|
||||||
print(f"GET Response: {response_text}")
|
logging.info(f"GET Response: {response_text}")
|
||||||
|
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
print(f"Ошибка при получении данных клиента: {response.status} - {response_text}")
|
logging.error(
|
||||||
|
f"Ошибка при получении данных клиента: {response.status} - {response_text}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
client_data = (await response.json()).get("obj", {})
|
client_data = (await response.json()).get("obj", {})
|
||||||
print(client_data)
|
logging.info(client_data)
|
||||||
|
|
||||||
if not client_data:
|
if not client_data:
|
||||||
print("Не удалось получить данные клиента.")
|
logging.error("Не удалось получить данные клиента.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
current_expiry_time = client_data.get('expiryTime', 0)
|
current_expiry_time = client_data.get("expiryTime", 0)
|
||||||
|
|
||||||
if current_expiry_time == 0:
|
if current_expiry_time == 0:
|
||||||
current_expiry_time = new_expiry_time
|
current_expiry_time = new_expiry_time
|
||||||
|
|
||||||
@@ -75,7 +94,60 @@ async def extend_client_key(session, server_id: str, tg_id, client_id, email: st
|
|||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"settings": json.dumps({
|
"settings": json.dumps(
|
||||||
|
{
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"id": client_id,
|
||||||
|
"alterId": 0,
|
||||||
|
"email": email.lower(),
|
||||||
|
"limitIp": 2,
|
||||||
|
"totalGB": 0,
|
||||||
|
"expiryTime": updated_expiry_time,
|
||||||
|
"enable": True,
|
||||||
|
"tgId": tg_id,
|
||||||
|
"subId": email,
|
||||||
|
"flow": "xtls-rprx-vision",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with session.post(
|
||||||
|
f"{api_url}/panel/api/inbounds/updateClient/{client_id}",
|
||||||
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
|
) as response:
|
||||||
|
logging.info(f"POST {response.url} Status: {response.status}")
|
||||||
|
logging.info(f"POST Request Data: {json.dumps(payload, indent=2)}")
|
||||||
|
response_text = await response.text()
|
||||||
|
logging.info(f"POST Response: {response_text}")
|
||||||
|
|
||||||
|
if response.status == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
f"Ошибка при продлении ключа: {response.status} - {response_text}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка запроса: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def extend_client_key_admin(
|
||||||
|
session, server_id: str, tg_id, client_id: str, email: str, new_expiry_time: int
|
||||||
|
) -> bool:
|
||||||
|
api_url = SERVERS[server_id]["API_URL"]
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"id": 1,
|
||||||
|
"settings": json.dumps(
|
||||||
|
{
|
||||||
"clients": [
|
"clients": [
|
||||||
{
|
{
|
||||||
"id": client_id,
|
"id": client_id,
|
||||||
@@ -83,95 +155,56 @@ async def extend_client_key(session, server_id: str, tg_id, client_id, email: st
|
|||||||
"email": email.lower(),
|
"email": email.lower(),
|
||||||
"limitIp": 2,
|
"limitIp": 2,
|
||||||
"totalGB": 0,
|
"totalGB": 0,
|
||||||
"expiryTime": updated_expiry_time,
|
"expiryTime": new_expiry_time,
|
||||||
"enable": True,
|
"enable": True,
|
||||||
"tgId": tg_id,
|
"tgId": tg_id,
|
||||||
"subId": email,
|
"subId": email,
|
||||||
"flow": "xtls-rprx-vision"
|
"flow": "xtls-rprx-vision",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}
|
||||||
}
|
),
|
||||||
|
}
|
||||||
headers = {
|
|
||||||
'Content-Type': 'application/json',
|
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with session.post(f"{api_url}/panel/api/inbounds/updateClient/{client_id}", json=payload, headers=headers) as response:
|
|
||||||
print(f"POST {response.url} Status: {response.status}")
|
|
||||||
print(f"POST Request Data: {json.dumps(payload, indent=2)}")
|
|
||||||
response_text = await response.text()
|
|
||||||
print(f"POST Response: {response_text}")
|
|
||||||
|
|
||||||
if response.status == 200:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"Ошибка при продлении ключа: {response.status} - {response_text}")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка запроса: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def extend_client_key_admin(session, server_id: str, tg_id, client_id: str, email: str, new_expiry_time: int) -> bool:
|
|
||||||
api_url = SERVERS[server_id]['API_URL']
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"id": 1,
|
|
||||||
"settings": json.dumps({
|
|
||||||
"clients": [
|
|
||||||
{
|
|
||||||
"id": client_id,
|
|
||||||
"alterId": 0,
|
|
||||||
"email": email.lower(),
|
|
||||||
"limitIp": 2,
|
|
||||||
"totalGB": 0,
|
|
||||||
"expiryTime": new_expiry_time,
|
|
||||||
"enable": True,
|
|
||||||
"tgId": tg_id,
|
|
||||||
"subId": email,
|
|
||||||
"flow": "xtls-rprx-vision"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with session.post(f"{api_url}/panel/api/inbounds/updateClient/{client_id}", json=payload, headers=headers) as response:
|
async with session.post(
|
||||||
print(f"POST {response.url} Status: {response.status}")
|
f"{api_url}/panel/api/inbounds/updateClient/{client_id}",
|
||||||
print(f"POST Request Data: {json.dumps(payload, indent=2)}")
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
|
) as response:
|
||||||
|
logging.info(f"POST {response.url} Status: {response.status}")
|
||||||
|
logging.info(f"POST Request Data: {json.dumps(payload, indent=2)}")
|
||||||
response_text = await response.text()
|
response_text = await response.text()
|
||||||
print(f"POST Response: {response_text}")
|
logging.info(f"POST Response: {response_text}")
|
||||||
|
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print(f"Ошибка при продлении ключа: {response.status} - {response_text}")
|
logging.error(
|
||||||
|
f"Ошибка при продлении ключа: {response.status} - {response_text}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка запроса: {e}")
|
logging.error(f"Ошибка запроса: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def delete_client(session, server_id: str, client_id: str) -> bool:
|
async def delete_client(session, server_id: str, client_id: str) -> bool:
|
||||||
api_url = SERVERS[server_id]['API_URL']
|
api_url = SERVERS[server_id]["API_URL"]
|
||||||
url = f"{api_url}/panel/api/inbounds/1/delClient/{client_id}"
|
url = f"{api_url}/panel/api/inbounds/1/delClient/{client_id}"
|
||||||
headers = {
|
headers = {"Accept": "application/json"}
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with session.post(url, headers=headers) as response:
|
async with session.post(url, headers=headers) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print(f"Ошибка при удалении клиента: {response.status} - {await response.text()}")
|
logging.error(
|
||||||
|
f"Ошибка при удалении клиента: {response.status} - {await response.text()}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка запроса: {e}")
|
logging.error(f"Ошибка запроса: {e}")
|
||||||
return False
|
return False
|
||||||
|
|||||||
+152
-60
@@ -7,16 +7,19 @@ from config import DATABASE_URL
|
|||||||
|
|
||||||
async def init_db():
|
async def init_db():
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
|
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS connections (
|
CREATE TABLE IF NOT EXISTS connections (
|
||||||
tg_id BIGINT PRIMARY KEY NOT NULL,
|
tg_id BIGINT PRIMARY KEY NOT NULL,
|
||||||
balance REAL NOT NULL DEFAULT 0.0,
|
balance REAL NOT NULL DEFAULT 0.0,
|
||||||
trial INTEGER NOT NULL DEFAULT 0
|
trial INTEGER NOT NULL DEFAULT 0
|
||||||
)
|
)
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
await conn.execute('''
|
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS keys (
|
CREATE TABLE IF NOT EXISTS keys (
|
||||||
tg_id BIGINT NOT NULL,
|
tg_id BIGINT NOT NULL,
|
||||||
client_id TEXT NOT NULL,
|
client_id TEXT NOT NULL,
|
||||||
@@ -28,182 +31,256 @@ async def init_db():
|
|||||||
notified BOOLEAN NOT NULL DEFAULT FALSE, -- новое поле для статуса уведомления
|
notified BOOLEAN NOT NULL DEFAULT FALSE, -- новое поле для статуса уведомления
|
||||||
PRIMARY KEY (tg_id, client_id)
|
PRIMARY KEY (tg_id, client_id)
|
||||||
)
|
)
|
||||||
''')
|
"""
|
||||||
await conn.execute('''
|
)
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS referrals (
|
CREATE TABLE IF NOT EXISTS referrals (
|
||||||
referred_tg_id BIGINT PRIMARY KEY NOT NULL, -- ID приглашенного пользователя
|
referred_tg_id BIGINT PRIMARY KEY NOT NULL, -- ID приглашенного пользователя
|
||||||
referrer_tg_id BIGINT NOT NULL, -- ID пригласившего пользователя
|
referrer_tg_id BIGINT NOT NULL, -- ID пригласившего пользователя
|
||||||
reward_issued BOOLEAN DEFAULT FALSE -- Был ли начислен бонус
|
reward_issued BOOLEAN DEFAULT FALSE -- Был ли начислен бонус
|
||||||
)
|
)
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
ALTER TABLE keys
|
ALTER TABLE keys
|
||||||
ADD COLUMN server_id TEXT NOT NULL DEFAULT 'server1'
|
ADD COLUMN server_id TEXT NOT NULL DEFAULT 'server1'
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
except asyncpg.exceptions.DuplicateColumnError:
|
except asyncpg.exceptions.DuplicateColumnError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
ALTER TABLE keys
|
ALTER TABLE keys
|
||||||
ADD COLUMN notified BOOLEAN NOT NULL DEFAULT FALSE
|
ADD COLUMN notified BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
except asyncpg.exceptions.DuplicateColumnError:
|
except asyncpg.exceptions.DuplicateColumnError:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
ALTER TABLE keys
|
ALTER TABLE keys
|
||||||
ADD COLUMN notified_24h BOOLEAN NOT NULL DEFAULT FALSE
|
ADD COLUMN notified_24h BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
except asyncpg.exceptions.DuplicateColumnError:
|
except asyncpg.exceptions.DuplicateColumnError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0):
|
async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
INSERT INTO connections (tg_id, balance, trial)
|
INSERT INTO connections (tg_id, balance, trial)
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
''', tg_id, balance, trial)
|
""",
|
||||||
|
tg_id,
|
||||||
|
balance,
|
||||||
|
trial,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def check_connection_exists(tg_id: int):
|
async def check_connection_exists(tg_id: int):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
exists = await conn.fetchval('''
|
exists = await conn.fetchval(
|
||||||
|
"""
|
||||||
SELECT EXISTS(SELECT 1 FROM connections WHERE tg_id = $1)
|
SELECT EXISTS(SELECT 1 FROM connections WHERE tg_id = $1)
|
||||||
''', tg_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return exists
|
return exists
|
||||||
|
|
||||||
async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str, server_id: str):
|
|
||||||
|
async def store_key(
|
||||||
|
tg_id: int, client_id: str, email: str, expiry_time: int, key: str, server_id: str
|
||||||
|
):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key, server_id)
|
INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key, server_id)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
''', tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key, server_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
client_id,
|
||||||
|
email,
|
||||||
|
int(datetime.utcnow().timestamp() * 1000),
|
||||||
|
expiry_time,
|
||||||
|
key,
|
||||||
|
server_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def get_keys(tg_id: int):
|
async def get_keys(tg_id: int):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
records = await conn.fetch('''
|
records = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT client_id, email, created_at, key
|
SELECT client_id, email, created_at, key
|
||||||
FROM keys
|
FROM keys
|
||||||
WHERE tg_id = $1
|
WHERE tg_id = $1
|
||||||
''', tg_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return records
|
return records
|
||||||
|
|
||||||
|
|
||||||
async def get_keys_by_server(tg_id: int, server_id: str):
|
async def get_keys_by_server(tg_id: int, server_id: str):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
records = await conn.fetch('''
|
records = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT client_id, email, created_at, key
|
SELECT client_id, email, created_at, key
|
||||||
FROM keys
|
FROM keys
|
||||||
WHERE tg_id = $1 AND server_id = $2
|
WHERE tg_id = $1 AND server_id = $2
|
||||||
''', tg_id, server_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
server_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return records
|
return records
|
||||||
|
|
||||||
|
|
||||||
async def has_active_key(tg_id: int) -> bool:
|
async def has_active_key(tg_id: int) -> bool:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id)
|
count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return count > 0
|
return count > 0
|
||||||
|
|
||||||
|
|
||||||
async def get_balance(tg_id: int) -> float:
|
async def get_balance(tg_id: int) -> float:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
balance = await conn.fetchval(
|
||||||
|
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return balance if balance is not None else 0.0
|
return balance if balance is not None else 0.0
|
||||||
|
|
||||||
|
|
||||||
async def update_balance(tg_id: int, amount: float):
|
async def update_balance(tg_id: int, amount: float):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
UPDATE connections
|
UPDATE connections
|
||||||
SET balance = balance + $1
|
SET balance = balance + $1
|
||||||
WHERE tg_id = $2
|
WHERE tg_id = $2
|
||||||
''', amount, tg_id)
|
""",
|
||||||
|
amount,
|
||||||
|
tg_id,
|
||||||
|
)
|
||||||
|
|
||||||
await handle_referral_on_balance_update(tg_id, amount)
|
await handle_referral_on_balance_update(tg_id, amount)
|
||||||
|
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def get_trial(tg_id: int) -> int:
|
async def get_trial(tg_id: int) -> int:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
trial = await conn.fetchval("SELECT trial FROM connections WHERE tg_id = $1", tg_id)
|
trial = await conn.fetchval("SELECT trial FROM connections WHERE tg_id = $1", tg_id)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return trial if trial is not None else 0
|
return trial if trial is not None else 0
|
||||||
|
|
||||||
|
|
||||||
async def get_key_count(tg_id: int) -> int:
|
async def get_key_count(tg_id: int) -> int:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE tg_id = $1', tg_id)
|
count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return count if count is not None else 0
|
return count if count is not None else 0
|
||||||
|
|
||||||
|
|
||||||
async def get_all_users(conn):
|
async def get_all_users(conn):
|
||||||
return await conn.fetch('SELECT tg_id FROM connections')
|
return await conn.fetch("SELECT tg_id FROM connections")
|
||||||
|
|
||||||
|
|
||||||
async def add_referral(referred_tg_id: int, referrer_tg_id: int):
|
async def add_referral(referred_tg_id: int, referrer_tg_id: int):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
INSERT INTO referrals (referred_tg_id, referrer_tg_id)
|
INSERT INTO referrals (referred_tg_id, referrer_tg_id)
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
''', referred_tg_id, referrer_tg_id)
|
""",
|
||||||
|
referred_tg_id,
|
||||||
|
referrer_tg_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def handle_referral_on_balance_update(tg_id: int, amount: float):
|
async def handle_referral_on_balance_update(tg_id: int, amount: float):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
|
|
||||||
referral = await conn.fetchrow('''
|
referral = await conn.fetchrow(
|
||||||
|
"""
|
||||||
SELECT referrer_tg_id FROM referrals WHERE referred_tg_id = $1
|
SELECT referrer_tg_id FROM referrals WHERE referred_tg_id = $1
|
||||||
''', tg_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
)
|
||||||
|
|
||||||
if referral:
|
if referral:
|
||||||
referrer_tg_id = referral['referrer_tg_id']
|
referrer_tg_id = referral["referrer_tg_id"]
|
||||||
|
|
||||||
bonus = amount * 0.25
|
bonus = amount * 0.25
|
||||||
|
|
||||||
if bonus < 0:
|
if bonus < 0:
|
||||||
bonus = 0
|
bonus = 0
|
||||||
|
|
||||||
await update_balance(referrer_tg_id, bonus)
|
await update_balance(referrer_tg_id, bonus)
|
||||||
|
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
UPDATE referrals SET reward_issued = TRUE
|
UPDATE referrals SET reward_issued = TRUE
|
||||||
WHERE referrer_tg_id = $1 AND referred_tg_id = $2
|
WHERE referrer_tg_id = $1 AND referred_tg_id = $2
|
||||||
''', referrer_tg_id, tg_id)
|
""",
|
||||||
|
referrer_tg_id,
|
||||||
|
tg_id,
|
||||||
|
)
|
||||||
|
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def get_referral_stats(referrer_tg_id: int):
|
async def get_referral_stats(referrer_tg_id: int):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
total_referrals = await conn.fetchval('''
|
total_referrals = await conn.fetchval(
|
||||||
|
"""
|
||||||
SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1
|
SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1
|
||||||
''', referrer_tg_id)
|
""",
|
||||||
|
referrer_tg_id,
|
||||||
|
)
|
||||||
|
|
||||||
active_referrals = await conn.fetchval('''
|
active_referrals = await conn.fetchval(
|
||||||
|
"""
|
||||||
SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1 AND reward_issued = TRUE
|
SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1 AND reward_issued = TRUE
|
||||||
''', referrer_tg_id)
|
""",
|
||||||
|
referrer_tg_id,
|
||||||
|
)
|
||||||
|
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
return {
|
return {"total_referrals": total_referrals, "active_referrals": active_referrals}
|
||||||
'total_referrals': total_referrals,
|
|
||||||
'active_referrals': active_referrals
|
|
||||||
}
|
|
||||||
|
|
||||||
async def update_key_expiry(client_id: str, new_expiry_time: int):
|
async def update_key_expiry(client_id: str, new_expiry_time: int):
|
||||||
"""
|
"""
|
||||||
Обновление времени истечения ключа на новое значение.
|
Обновление времени истечения ключа на новое значение.
|
||||||
"""
|
"""
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
UPDATE keys
|
UPDATE keys
|
||||||
SET expiry_time = $1, notified = FALSE, notified_24h = FALSE
|
SET expiry_time = $1, notified = FALSE, notified_24h = FALSE
|
||||||
WHERE client_id = $2
|
WHERE client_id = $2
|
||||||
''', new_expiry_time, client_id)
|
""",
|
||||||
|
new_expiry_time,
|
||||||
|
client_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
@@ -212,36 +289,51 @@ async def delete_key(client_id: str):
|
|||||||
Удаление ключа из базы данных.
|
Удаление ключа из базы данных.
|
||||||
"""
|
"""
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
DELETE FROM keys
|
DELETE FROM keys
|
||||||
WHERE client_id = $1
|
WHERE client_id = $1
|
||||||
''', client_id)
|
""",
|
||||||
|
client_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def add_balance_to_client(client_id: str, amount: float):
|
async def add_balance_to_client(client_id: str, amount: float):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
UPDATE connections
|
UPDATE connections
|
||||||
SET balance = balance + $1
|
SET balance = balance + $1
|
||||||
WHERE tg_id = $2
|
WHERE tg_id = $2
|
||||||
''', amount, client_id)
|
""",
|
||||||
|
amount,
|
||||||
|
client_id,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def get_client_id_by_email(email: str):
|
async def get_client_id_by_email(email: str):
|
||||||
"""
|
"""
|
||||||
Получение client_id по email.
|
Получение client_id по email.
|
||||||
"""
|
"""
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
client_id = await conn.fetchval('''
|
client_id = await conn.fetchval(
|
||||||
|
"""
|
||||||
SELECT client_id FROM keys WHERE email = $1
|
SELECT client_id FROM keys WHERE email = $1
|
||||||
''', email)
|
""",
|
||||||
|
email,
|
||||||
|
)
|
||||||
await conn.close()
|
await conn.close()
|
||||||
return client_id
|
return client_id
|
||||||
|
|
||||||
|
|
||||||
async def get_tg_id_by_client_id(client_id: str):
|
async def get_tg_id_by_client_id(client_id: str):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
result = await conn.fetchrow('SELECT tg_id FROM keys WHERE client_id = $1', client_id)
|
result = await conn.fetchrow(
|
||||||
return result['tg_id'] if result else None
|
"SELECT tg_id FROM keys WHERE client_id = $1", client_id
|
||||||
|
)
|
||||||
|
return result["tg_id"] if result else None
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|||||||
+57
-31
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import asyncpg
|
import asyncpg
|
||||||
@@ -6,19 +7,24 @@ from aiogram.filters import Command
|
|||||||
|
|
||||||
from auth import login_with_credentials
|
from auth import login_with_credentials
|
||||||
from client import extend_client_key_admin
|
from client import extend_client_key_admin
|
||||||
from config import ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
from config import ADMIN_IDS, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||||
from database import (add_balance_to_client, check_connection_exists,
|
from database import (
|
||||||
get_client_id_by_email, get_tg_id_by_client_id,
|
add_balance_to_client,
|
||||||
update_key_expiry)
|
check_connection_exists,
|
||||||
|
get_client_id_by_email,
|
||||||
|
get_tg_id_by_client_id,
|
||||||
|
update_key_expiry,
|
||||||
|
)
|
||||||
|
from middlewares.admin import admin_only
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.message(Command('add_balance'))
|
|
||||||
|
@router.message(Command("add_balance"))
|
||||||
|
@admin_only()
|
||||||
async def cmd_add_balance(message: types.Message):
|
async def cmd_add_balance(message: types.Message):
|
||||||
if message.from_user.id != ADMIN_ID:
|
|
||||||
await message.reply("У вас нет доступа к этой команде.")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_, client_id, amount = message.text.split()
|
_, client_id, amount = message.text.split()
|
||||||
amount = float(amount)
|
amount = float(amount)
|
||||||
@@ -30,24 +36,28 @@ async def cmd_add_balance(message: types.Message):
|
|||||||
await add_balance_to_client(int(client_id), amount)
|
await add_balance_to_client(int(client_id), amount)
|
||||||
await message.reply(f"Баланс клиента {client_id} увеличен на {amount} у.е.")
|
await message.reply(f"Баланс клиента {client_id} увеличен на {amount} у.е.")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.reply("Пожалуйста, используйте формат: /add_balance <client_id> <amount>")
|
await message.reply(
|
||||||
|
"Пожалуйста, используйте формат: /add_balance <client_id> <amount>"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.reply(f"Произошла ошибка: {e}")
|
await message.reply(f"Произошла ошибка: {e}")
|
||||||
|
|
||||||
@router.message(Command('update_key_expiry'))
|
|
||||||
|
@router.message(Command("update_key_expiry"))
|
||||||
|
@admin_only()
|
||||||
async def cmd_update_key_expiry(message: types.Message):
|
async def cmd_update_key_expiry(message: types.Message):
|
||||||
if message.from_user.id != ADMIN_ID:
|
|
||||||
await message.reply("У вас нет доступа к этой команде.")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parts = message.text.split(maxsplit=2)
|
parts = message.text.split(maxsplit=2)
|
||||||
if len(parts) != 3:
|
if len(parts) != 3:
|
||||||
await message.reply("Пожалуйста, используйте формат: /update_key_expiry <email> <expiry_time(YYYY-MM-DD HH:MM:SS)>")
|
await message.reply(
|
||||||
|
"Пожалуйста, используйте формат: /update_key_expiry <email> <expiry_time(YYYY-MM-DD HH:MM:SS)>"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
_, email, expiry_time_str = parts
|
_, email, expiry_time_str = parts
|
||||||
expiry_time = int(datetime.strptime(expiry_time_str, '%Y-%m-%d %H:%M:%S').timestamp() * 1000)
|
expiry_time = int(
|
||||||
|
datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000
|
||||||
|
)
|
||||||
|
|
||||||
client_id = await get_client_id_by_email(email)
|
client_id = await get_client_id_by_email(email)
|
||||||
if client_id is None:
|
if client_id is None:
|
||||||
@@ -55,32 +65,48 @@ async def cmd_update_key_expiry(message: types.Message):
|
|||||||
return
|
return
|
||||||
|
|
||||||
await update_key_expiry(client_id, expiry_time)
|
await update_key_expiry(client_id, expiry_time)
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('SELECT server_id FROM keys WHERE client_id = $1', client_id)
|
record = await conn.fetchrow(
|
||||||
|
"SELECT server_id FROM keys WHERE client_id = $1", client_id
|
||||||
|
)
|
||||||
if not record:
|
if not record:
|
||||||
await message.reply("Клиент не найден в базе данных.")
|
await message.reply("Клиент не найден в базе данных.")
|
||||||
return
|
return
|
||||||
|
|
||||||
server_id = record['server_id']
|
server_id = record["server_id"]
|
||||||
tg_id = await get_tg_id_by_client_id(client_id)
|
tg_id = await get_tg_id_by_client_id(client_id)
|
||||||
|
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
print(f"Попытка обновить панель для server_id: {server_id}, tg_id: {tg_id}, client_id: {client_id}, email: {email}, expiryTime: {expiry_time}")
|
)
|
||||||
|
|
||||||
success = await extend_client_key_admin(session, server_id, tg_id, client_id, email, expiry_time)
|
logging.info(
|
||||||
|
f"Попытка обновить панель для server_id: {server_id}, tg_id: {tg_id}, client_id: {client_id}, email: {email}, expiryTime: {expiry_time}"
|
||||||
|
)
|
||||||
|
|
||||||
print(f"Статус обновления панели: {'Успешно' if success else 'Не удалось'}")
|
success = await extend_client_key_admin(
|
||||||
|
session, server_id, tg_id, client_id, email, expiry_time
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(
|
||||||
|
f"Статус обновления панели: {'Успешно' if success else 'Не удалось'}"
|
||||||
|
)
|
||||||
if success:
|
if success:
|
||||||
await message.reply(f"Время истечения ключа для клиента {client_id} ({email}) обновлено и синхронизировано с панелью.")
|
await message.reply(
|
||||||
|
f"Время истечения ключа для клиента {client_id} ({email}) обновлено и синхронизировано с панелью."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await message.reply(f"Время истечения ключа для клиента {client_id} ({email}) обновлено, но не удалось синхронизировать с панелью.")
|
await message.reply(
|
||||||
|
f"Время истечения ключа для клиента {client_id} ({email}) обновлено, но не удалось синхронизировать с панелью."
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.reply("Пожалуйста, используйте формат: /update_key_expiry <email> <expiry_time(YYYY-MM-DD HH:MM:SS)>")
|
await message.reply(
|
||||||
|
"Пожалуйста, используйте формат: /update_key_expiry <email> <expiry_time(YYYY-MM-DD HH:MM:SS)>"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.reply(f"Произошла ошибка: {e}")
|
await message.reply(f"Произошла ошибка: {e}")
|
||||||
|
|||||||
+125
-39
@@ -6,37 +6,63 @@ from aiogram import Router, types
|
|||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
from aiogram.types import (CallbackQuery, InlineKeyboardButton,
|
from aiogram.types import (
|
||||||
InlineKeyboardMarkup, Message)
|
CallbackQuery,
|
||||||
|
InlineKeyboardButton,
|
||||||
|
InlineKeyboardMarkup,
|
||||||
|
Message,
|
||||||
|
)
|
||||||
|
|
||||||
from backup import backup_database
|
from backup import backup_database
|
||||||
from bot import bot
|
from bot import bot
|
||||||
from config import ADMIN_ID, DATABASE_URL
|
from config import ADMIN_ID, DATABASE_URL
|
||||||
from handlers.commands import send_message_to_all_clients
|
from handlers.commands import send_message_to_all_clients
|
||||||
|
from middlewares.admin import admin_only
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
class UserEditorState(StatesGroup):
|
class UserEditorState(StatesGroup):
|
||||||
waiting_for_tg_id = State()
|
waiting_for_tg_id = State()
|
||||||
displaying_user_info = State()
|
displaying_user_info = State()
|
||||||
|
|
||||||
@router.message(Command('admin'))
|
|
||||||
async def handle_admin_command(message: types.Message):
|
|
||||||
if message.from_user.id != ADMIN_ID:
|
|
||||||
await bot.send_message(message.chat.id, "У вас нет доступа к этой команде.")
|
|
||||||
return
|
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
@router.message(Command("admin"))
|
||||||
[InlineKeyboardButton(text="Статистика пользователей", callback_data="user_stats")],
|
@admin_only()
|
||||||
[InlineKeyboardButton(text="Редактор пользователей", callback_data="user_editor")],
|
async def handle_admin_command(message: types.Message):
|
||||||
[InlineKeyboardButton(text="Отправить сообщение всем клиентам", callback_data="send_to_alls")],
|
keyboard = InlineKeyboardMarkup(
|
||||||
[InlineKeyboardButton(text="Создать бэкап", callback_data="backups")],
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Перезапустить бота", callback_data="restart_bot")]
|
[
|
||||||
])
|
InlineKeyboardButton(
|
||||||
await bot.send_message(message.chat.id, "Панель администратора", reply_markup=keyboard)
|
text="Статистика пользователей", callback_data="user_stats"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Редактор пользователей", callback_data="user_editor"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Отправить сообщение всем клиентам",
|
||||||
|
callback_data="send_to_alls",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="Создать бэкап", callback_data="backups")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Перезапустить бота", callback_data="restart_bot"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id, "Панель администратора", reply_markup=keyboard
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "user_stats")
|
@router.callback_query(lambda c: c.data == "user_stats")
|
||||||
|
@admin_only()
|
||||||
async def user_stats_menu(callback_query: CallbackQuery):
|
async def user_stats_menu(callback_query: CallbackQuery):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
@@ -44,7 +70,10 @@ async def user_stats_menu(callback_query: CallbackQuery):
|
|||||||
total_keys = await conn.fetchval("SELECT COUNT(*) FROM keys")
|
total_keys = await conn.fetchval("SELECT COUNT(*) FROM keys")
|
||||||
total_referrals = await conn.fetchval("SELECT COUNT(*) FROM referrals")
|
total_referrals = await conn.fetchval("SELECT COUNT(*) FROM referrals")
|
||||||
|
|
||||||
active_keys = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE expiry_time > $1", int(datetime.utcnow().timestamp() * 1000))
|
active_keys = await conn.fetchval(
|
||||||
|
"SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
|
||||||
|
int(datetime.utcnow().timestamp() * 1000),
|
||||||
|
)
|
||||||
expired_keys = total_keys - active_keys
|
expired_keys = total_keys - active_keys
|
||||||
|
|
||||||
stats_message = (
|
stats_message = (
|
||||||
@@ -56,49 +85,84 @@ async def user_stats_menu(callback_query: CallbackQuery):
|
|||||||
f"• Истекшие ключи: <b>{expired_keys}</b>"
|
f"• Истекшие ключи: <b>{expired_keys}</b>"
|
||||||
)
|
)
|
||||||
|
|
||||||
back_button = InlineKeyboardButton(text="Назад", callback_data="back_to_admin_menu")
|
back_button = InlineKeyboardButton(
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
text="Назад", callback_data="back_to_admin_menu"
|
||||||
[back_button]
|
)
|
||||||
])
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await callback_query.message.edit_text(stats_message, reply_markup=keyboard, parse_mode="HTML")
|
await callback_query.message.edit_text(
|
||||||
|
stats_message, reply_markup=keyboard, parse_mode="HTML"
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "send_to_alls")
|
@router.callback_query(lambda c: c.data == "send_to_alls")
|
||||||
|
@admin_only()
|
||||||
async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
|
async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
|
||||||
await send_message_to_all_clients(callback_query.message, state, from_panel=True)
|
await send_message_to_all_clients(callback_query.message, state, from_panel=True)
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "backups")
|
@router.callback_query(lambda c: c.data == "backups")
|
||||||
|
@admin_only()
|
||||||
async def handle_backup(message: Message):
|
async def handle_backup(message: Message):
|
||||||
await message.answer("Запускаю бэкап базы данных...")
|
await message.answer("Запускаю бэкап базы данных...")
|
||||||
await backup_database()
|
await backup_database()
|
||||||
await message.answer("Бэкап завершен и отправлен админу.")
|
await message.answer("Бэкап завершен и отправлен админу.")
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "restart_bot")
|
@router.callback_query(lambda c: c.data == "restart_bot")
|
||||||
|
@admin_only()
|
||||||
async def handle_restart(callback_query: CallbackQuery):
|
async def handle_restart(callback_query: CallbackQuery):
|
||||||
if callback_query.from_user.id == ADMIN_ID:
|
if callback_query.from_user.id == ADMIN_ID:
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(['sudo', 'systemctl', 'restart', 'bot.service'], check=True, capture_output=True, text=True)
|
result = subprocess.run(
|
||||||
|
["sudo", "systemctl", "restart", "bot.service"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
await callback_query.message.answer("Бот успешно перезапущен.")
|
await callback_query.message.answer("Бот успешно перезапущен.")
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
await callback_query.message.answer(f"Бот будет перезапущен через 30 секунд {e.stderr}")
|
await callback_query.message.answer(
|
||||||
|
f"Бот будет перезапущен через 30 секунд {e.stderr}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await callback_query.answer("У вас нет доступа к этой команде.", show_alert=True)
|
await callback_query.answer(
|
||||||
|
"У вас нет доступа к этой команде.", show_alert=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "user_editor")
|
@router.callback_query(lambda c: c.data == "user_editor")
|
||||||
|
@admin_only()
|
||||||
async def user_editor_menu(callback_query: CallbackQuery):
|
async def user_editor_menu(callback_query: CallbackQuery):
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(
|
||||||
[InlineKeyboardButton(text="Поиск по имени ключа", callback_data="search_by_key_name")],
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Поиск по tg_id", callback_data="search_by_tg_id")],
|
[
|
||||||
[InlineKeyboardButton(text="Назад", callback_data="back_to_admin_menu")] # Back button
|
InlineKeyboardButton(
|
||||||
])
|
text="Поиск по имени ключа", callback_data="search_by_key_name"
|
||||||
await callback_query.message.edit_text("Выберите метод поиска:", reply_markup=keyboard)
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Поиск по tg_id", callback_data="search_by_tg_id"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(text="Назад", callback_data="back_to_admin_menu")
|
||||||
|
], # Back button
|
||||||
|
]
|
||||||
|
)
|
||||||
|
await callback_query.message.edit_text(
|
||||||
|
"Выберите метод поиска:", reply_markup=keyboard
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "back_to_admin_menu")
|
@router.callback_query(lambda c: c.data == "back_to_admin_menu")
|
||||||
|
@admin_only()
|
||||||
async def back_to_admin_menu(callback_query: CallbackQuery):
|
async def back_to_admin_menu(callback_query: CallbackQuery):
|
||||||
try:
|
try:
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
@@ -107,16 +171,38 @@ async def back_to_admin_menu(callback_query: CallbackQuery):
|
|||||||
|
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
if tg_id == ADMIN_ID:
|
if tg_id == ADMIN_ID:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(
|
||||||
[InlineKeyboardButton(text="Статистика пользователей", callback_data="user_stats")],
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Редактор пользователей", callback_data="user_editor")],
|
[
|
||||||
[InlineKeyboardButton(text="Отправить сообщение всем клиентам", callback_data="send_to_alls")],
|
InlineKeyboardButton(
|
||||||
[InlineKeyboardButton(text="Создать бэкап", callback_data="backups")],
|
text="Статистика пользователей", callback_data="user_stats"
|
||||||
[InlineKeyboardButton(text="Перезапустить бота", callback_data="restart_bot")]
|
)
|
||||||
])
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Редактор пользователей", callback_data="user_editor"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Отправить сообщение всем клиентам",
|
||||||
|
callback_data="send_to_alls",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="Создать бэкап", callback_data="backups")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Перезапустить бота", callback_data="restart_bot"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
await bot.send_message(tg_id, "Панель администратора", reply_markup=keyboard)
|
await bot.send_message(tg_id, "Панель администратора", reply_markup=keyboard)
|
||||||
else:
|
else:
|
||||||
await bot.send_message(tg_id, "У вас нет доступа к этой команде.")
|
await bot.send_message(tg_id, "У вас нет доступа к этой команде.")
|
||||||
|
|
||||||
|
|
||||||
async def handle_error(tg_id, callback_query, message):
|
async def handle_error(tg_id, callback_query, message):
|
||||||
await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.edit_message_text(
|
||||||
|
message, chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
|||||||
+242
-103
@@ -1,50 +1,56 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import asyncpg
|
import asyncpg
|
||||||
from aiogram import F, Router, types
|
from aiogram import F, Router, types
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
from aiogram.types import (CallbackQuery, InlineKeyboardButton,
|
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
InlineKeyboardMarkup)
|
|
||||||
|
|
||||||
from auth import login_with_credentials
|
from auth import login_with_credentials
|
||||||
from bot import bot
|
from bot import bot
|
||||||
from client import delete_client, extend_client_key_admin
|
from client import delete_client, extend_client_key_admin
|
||||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
|
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
|
||||||
from database import (get_client_id_by_email, get_tg_id_by_client_id,
|
from database import get_client_id_by_email, get_tg_id_by_client_id, update_key_expiry
|
||||||
update_key_expiry)
|
|
||||||
from handlers.admin.admin_panel import back_to_admin_menu
|
from handlers.admin.admin_panel import back_to_admin_menu
|
||||||
from handlers.utils import sanitize_key_name
|
from handlers.utils import sanitize_key_name
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
class UserEditorState(StatesGroup):
|
class UserEditorState(StatesGroup):
|
||||||
waiting_for_tg_id = State()
|
waiting_for_tg_id = State()
|
||||||
displaying_user_info = State()
|
displaying_user_info = State()
|
||||||
waiting_for_new_balance = State()
|
waiting_for_new_balance = State()
|
||||||
waiting_for_key_name = State()
|
waiting_for_key_name = State()
|
||||||
waiting_for_expiry_time = State()
|
waiting_for_expiry_time = State()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "search_by_tg_id")
|
@router.callback_query(lambda c: c.data == "search_by_tg_id")
|
||||||
async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext):
|
async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext):
|
||||||
await callback_query.message.edit_text("Введите tg_id клиента:")
|
await callback_query.message.edit_text("Введите tg_id клиента:")
|
||||||
await state.set_state(UserEditorState.waiting_for_tg_id)
|
await state.set_state(UserEditorState.waiting_for_tg_id)
|
||||||
|
|
||||||
|
|
||||||
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit())
|
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit())
|
||||||
async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
||||||
tg_id = int(message.text)
|
tg_id = int(message.text)
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
balance = await conn.fetchval(
|
||||||
|
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||||
|
)
|
||||||
key_records = await conn.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
key_records = await conn.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||||
referral_count = await conn.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
referral_count = await conn.fetchval(
|
||||||
|
"SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id
|
||||||
|
)
|
||||||
|
|
||||||
if balance is None:
|
if balance is None:
|
||||||
await message.reply("Пользователь с указанным tg_id не найден.")
|
await message.reply("Пользователь с указанным tg_id не найден.")
|
||||||
@@ -55,11 +61,22 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
|||||||
[InlineKeyboardButton(text=email, callback_data=f"edit_key_{email}")]
|
[InlineKeyboardButton(text=email, callback_data=f"edit_key_{email}")]
|
||||||
for email, in key_records
|
for email, in key_records
|
||||||
]
|
]
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(
|
||||||
*key_buttons,
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}")],
|
*key_buttons,
|
||||||
[InlineKeyboardButton(text="Назад", callback_data="back_to_user_editor")]
|
[
|
||||||
])
|
InlineKeyboardButton(
|
||||||
|
text="📝 Изменить баланс",
|
||||||
|
callback_data=f"change_balance_{tg_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="back_to_user_editor"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
user_info = (
|
user_info = (
|
||||||
f"Информация о пользователе:\n"
|
f"Информация о пользователе:\n"
|
||||||
@@ -73,32 +90,40 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext):
|
|||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('change_balance_'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("change_balance_"))
|
||||||
async def process_balance_change(callback_query: CallbackQuery, state: FSMContext):
|
async def process_balance_change(callback_query: CallbackQuery, state: FSMContext):
|
||||||
tg_id = int(callback_query.data.split('_')[2])
|
tg_id = int(callback_query.data.split("_")[2])
|
||||||
await state.update_data(tg_id=tg_id)
|
await state.update_data(tg_id=tg_id)
|
||||||
|
|
||||||
await callback_query.message.edit_text("Введите новую сумму баланса:")
|
await callback_query.message.edit_text("Введите новую сумму баланса:")
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
await state.set_state(UserEditorState.waiting_for_new_balance)
|
await state.set_state(UserEditorState.waiting_for_new_balance)
|
||||||
|
|
||||||
|
|
||||||
@router.message(UserEditorState.waiting_for_new_balance)
|
@router.message(UserEditorState.waiting_for_new_balance)
|
||||||
async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
||||||
if not message.text.isdigit() or int(message.text) < 0:
|
if not message.text.isdigit() or int(message.text) < 0:
|
||||||
await message.reply("Пожалуйста, введите корректную сумму для изменения баланса.")
|
await message.reply(
|
||||||
|
"Пожалуйста, введите корректную сумму для изменения баланса."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
new_balance = int(message.text)
|
new_balance = int(message.text)
|
||||||
user_data = await state.get_data()
|
user_data = await state.get_data()
|
||||||
tg_id = user_data.get('tg_id')
|
tg_id = user_data.get("tg_id")
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
await conn.execute("UPDATE connections SET balance = $1 WHERE tg_id = $2", new_balance, tg_id)
|
await conn.execute(
|
||||||
|
"UPDATE connections SET balance = $1 WHERE tg_id = $2", new_balance, tg_id
|
||||||
|
)
|
||||||
|
|
||||||
response_message = f"Баланс успешно изменен на <b>{new_balance}</b>."
|
response_message = f"Баланс успешно изменен на <b>{new_balance}</b>."
|
||||||
|
|
||||||
back_button = InlineKeyboardButton(text="Назад в меню админа", callback_data="back_to_user_editor")
|
back_button = InlineKeyboardButton(
|
||||||
|
text="Назад в меню админа", callback_data="back_to_user_editor"
|
||||||
|
)
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await message.reply(response_message, reply_markup=keyboard, parse_mode="HTML")
|
await message.reply(response_message, reply_markup=keyboard, parse_mode="HTML")
|
||||||
@@ -106,27 +131,32 @@ async def handle_new_balance_input(message: types.Message, state: FSMContext):
|
|||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('edit_key_'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("edit_key_"))
|
||||||
async def process_key_edit(callback_query: CallbackQuery):
|
async def process_key_edit(callback_query: CallbackQuery):
|
||||||
email = callback_query.data.split('_', 2)[2]
|
email = callback_query.data.split("_", 2)[2]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('''
|
record = await conn.fetchrow(
|
||||||
|
"""
|
||||||
SELECT k.key, k.expiry_time, k.server_id
|
SELECT k.key, k.expiry_time, k.server_id
|
||||||
FROM keys k
|
FROM keys k
|
||||||
WHERE k.email = $1
|
WHERE k.email = $1
|
||||||
''', email)
|
""",
|
||||||
|
email,
|
||||||
|
)
|
||||||
|
|
||||||
if record:
|
if record:
|
||||||
key = record['key']
|
key = record["key"]
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
server_id = record['server_id']
|
server_id = record["server_id"]
|
||||||
server_name = SERVERS.get(server_id, {}).get('name', 'Неизвестный сервер')
|
server_name = SERVERS.get(server_id, {}).get(
|
||||||
|
"name", "Неизвестный сервер"
|
||||||
|
)
|
||||||
|
|
||||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||||
current_date = datetime.utcnow()
|
current_date = datetime.utcnow()
|
||||||
@@ -140,7 +170,7 @@ async def process_key_edit(callback_query: CallbackQuery):
|
|||||||
hours_left = time_left.seconds // 3600
|
hours_left = time_left.seconds // 3600
|
||||||
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
||||||
|
|
||||||
formatted_expiry_date = expiry_date.strftime('%d %B %Y года')
|
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||||
|
|
||||||
response_message = (
|
response_message = (
|
||||||
f"Ключ: <pre>{key}</pre>\n"
|
f"Ключ: <pre>{key}</pre>\n"
|
||||||
@@ -149,45 +179,67 @@ async def process_key_edit(callback_query: CallbackQuery):
|
|||||||
f"Сервер: <b>{server_name}</b>"
|
f"Сервер: <b>{server_name}</b>"
|
||||||
)
|
)
|
||||||
|
|
||||||
change_expiry_button = types.InlineKeyboardButton(text='⏳ Изменить время истечения', callback_data=f'change_expiry|{email}')
|
change_expiry_button = types.InlineKeyboardButton(
|
||||||
delete_button = types.InlineKeyboardButton(text='❌ Удалить ключ', callback_data=f'delete_key_admin|{email}')
|
text="⏳ Изменить время истечения",
|
||||||
|
callback_data=f"change_expiry|{email}",
|
||||||
|
)
|
||||||
|
delete_button = types.InlineKeyboardButton(
|
||||||
|
text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}"
|
||||||
|
)
|
||||||
|
|
||||||
keyboard = types.InlineKeyboardMarkup(
|
keyboard = types.InlineKeyboardMarkup(
|
||||||
inline_keyboard=[
|
inline_keyboard=[
|
||||||
[change_expiry_button, delete_button],
|
[change_expiry_button, delete_button],
|
||||||
[InlineKeyboardButton(text="Назад", callback_data="back_to_user_editor")] # Кнопка "Назад"
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="back_to_user_editor"
|
||||||
|
)
|
||||||
|
], # Кнопка "Назад"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback_query.message.edit_text(response_message, reply_markup=keyboard, parse_mode="HTML")
|
await callback_query.message.edit_text(
|
||||||
|
response_message, reply_markup=keyboard, parse_mode="HTML"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await callback_query.message.edit_text("<b>Информация о ключе не найдена.</b>", parse_mode="HTML")
|
await callback_query.message.edit_text(
|
||||||
|
"<b>Информация о ключе не найдена.</b>", parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await handle_error(callback_query.from_user.id, callback_query, f"Ошибка при получении информации о ключе: {e}")
|
await handle_error(
|
||||||
|
callback_query.from_user.id,
|
||||||
|
callback_query,
|
||||||
|
f"Ошибка при получении информации о ключе: {e}",
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "search_by_key_name")
|
@router.callback_query(lambda c: c.data == "search_by_key_name")
|
||||||
async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext):
|
async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext):
|
||||||
await callback_query.message.edit_text("Введите имя ключа:")
|
await callback_query.message.edit_text("Введите имя ключа:")
|
||||||
await state.set_state(UserEditorState.waiting_for_key_name)
|
await state.set_state(UserEditorState.waiting_for_key_name)
|
||||||
|
|
||||||
|
|
||||||
@router.message(UserEditorState.waiting_for_key_name)
|
@router.message(UserEditorState.waiting_for_key_name)
|
||||||
async def handle_key_name_input(message: types.Message, state: FSMContext):
|
async def handle_key_name_input(message: types.Message, state: FSMContext):
|
||||||
key_name = sanitize_key_name(message.text)
|
key_name = sanitize_key_name(message.text)
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
user_records = await conn.fetch('''
|
user_records = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT c.tg_id, c.balance, k.email, k.key, k.expiry_time, k.server_id
|
SELECT c.tg_id, c.balance, k.email, k.key, k.expiry_time, k.server_id
|
||||||
FROM connections c
|
FROM connections c
|
||||||
JOIN keys k ON c.tg_id = k.tg_id
|
JOIN keys k ON c.tg_id = k.tg_id
|
||||||
WHERE k.email = $1
|
WHERE k.email = $1
|
||||||
''', key_name)
|
""",
|
||||||
|
key_name,
|
||||||
|
)
|
||||||
|
|
||||||
if not user_records:
|
if not user_records:
|
||||||
await message.reply("Пользователь с указанным именем ключа не найден.")
|
await message.reply("Пользователь с указанным именем ключа не найден.")
|
||||||
@@ -198,15 +250,17 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
|
|||||||
key_buttons = []
|
key_buttons = []
|
||||||
|
|
||||||
for record in user_records:
|
for record in user_records:
|
||||||
tg_id = record['tg_id']
|
tg_id = record["tg_id"]
|
||||||
balance = record['balance']
|
balance = record["balance"]
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
key = record['key']
|
key = record["key"]
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
server_id = record['server_id']
|
server_id = record["server_id"]
|
||||||
server_name = SERVERS.get(server_id, {}).get('name', 'Неизвестный сервер')
|
server_name = SERVERS.get(server_id, {}).get("name", "Неизвестный сервер")
|
||||||
|
|
||||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime('%d %B %Y')
|
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime(
|
||||||
|
"%d %B %Y"
|
||||||
|
)
|
||||||
|
|
||||||
response_messages.append(
|
response_messages.append(
|
||||||
f"Ключ: <pre>{key}</pre>\n"
|
f"Ключ: <pre>{key}</pre>\n"
|
||||||
@@ -215,36 +269,47 @@ async def handle_key_name_input(message: types.Message, state: FSMContext):
|
|||||||
f"Сервер: <b>{server_name}</b>"
|
f"Сервер: <b>{server_name}</b>"
|
||||||
)
|
)
|
||||||
|
|
||||||
change_expiry_button = InlineKeyboardButton(text='⏳ Изменить время истечения', callback_data=f'change_expiry|{email}')
|
change_expiry_button = InlineKeyboardButton(
|
||||||
delete_button = InlineKeyboardButton(text='❌ Удалить ключ', callback_data=f'delete_key_admin|{email}')
|
text="⏳ Изменить время истечения",
|
||||||
|
callback_data=f"change_expiry|{email}",
|
||||||
|
)
|
||||||
|
delete_button = InlineKeyboardButton(
|
||||||
|
text="❌ Удалить ключ", callback_data=f"delete_key_admin|{email}"
|
||||||
|
)
|
||||||
|
|
||||||
key_buttons.append([change_expiry_button, delete_button])
|
key_buttons.append([change_expiry_button, delete_button])
|
||||||
|
|
||||||
key_buttons.append([InlineKeyboardButton(text="Назад", callback_data="back_to_user_editor")])
|
key_buttons.append(
|
||||||
|
[InlineKeyboardButton(text="Назад", callback_data="back_to_user_editor")]
|
||||||
|
)
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=key_buttons)
|
keyboard = InlineKeyboardMarkup(inline_keyboard=key_buttons)
|
||||||
|
|
||||||
await message.reply("\n".join(response_messages), reply_markup=keyboard, parse_mode="HTML")
|
await message.reply(
|
||||||
|
"\n".join(response_messages), reply_markup=keyboard, parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('change_expiry|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("change_expiry|"))
|
||||||
async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext):
|
async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext):
|
||||||
email = callback_query.data.split('|')[1]
|
email = callback_query.data.split("|")[1]
|
||||||
await callback_query.message.edit_text(
|
await callback_query.message.edit_text(
|
||||||
f"Введите новое время истечения для ключа <b>{email}</b> в формате <code>YYYY-MM-DD HH:MM:SS</code>:",
|
f"Введите новое время истечения для ключа <b>{email}</b> в формате <code>YYYY-MM-DD HH:MM:SS</code>:",
|
||||||
parse_mode="HTML"
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
await state.update_data(email=email)
|
await state.update_data(email=email)
|
||||||
await state.set_state(UserEditorState.waiting_for_expiry_time)
|
await state.set_state(UserEditorState.waiting_for_expiry_time)
|
||||||
|
|
||||||
|
|
||||||
@router.message(UserEditorState.waiting_for_expiry_time)
|
@router.message(UserEditorState.waiting_for_expiry_time)
|
||||||
async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
||||||
user_data = await state.get_data()
|
user_data = await state.get_data()
|
||||||
email = user_data.get('email')
|
email = user_data.get("email")
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
await message.reply("Email не найден в состоянии.")
|
await message.reply("Email не найден в состоянии.")
|
||||||
@@ -253,9 +318,11 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
expiry_time_str = message.text
|
expiry_time_str = message.text
|
||||||
expiry_time = int(datetime.strptime(expiry_time_str, '%Y-%m-%d %H:%M:%S').timestamp() * 1000)
|
expiry_time = int(
|
||||||
|
datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000
|
||||||
|
)
|
||||||
|
|
||||||
client_id = await get_client_id_by_email(email)
|
client_id = await get_client_id_by_email(email)
|
||||||
if client_id is None:
|
if client_id is None:
|
||||||
await message.reply(f"Клиент с email {email} не найден.")
|
await message.reply(f"Клиент с email {email} не найден.")
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -263,7 +330,9 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
|||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('SELECT server_id FROM keys WHERE client_id = $1', client_id)
|
record = await conn.fetchrow(
|
||||||
|
"SELECT server_id FROM keys WHERE client_id = $1", client_id
|
||||||
|
)
|
||||||
if not record:
|
if not record:
|
||||||
await message.reply("Клиент не найден в базе данных.")
|
await message.reply("Клиент не найден в базе данных.")
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -274,23 +343,29 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
|||||||
async def update_key_on_all_servers():
|
async def update_key_on_all_servers():
|
||||||
tasks = []
|
tasks = []
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
tasks.append(asyncio.create_task(
|
tasks.append(
|
||||||
renew_server_key(server_id, tg_id, client_id, email, expiry_time)
|
asyncio.create_task(
|
||||||
))
|
renew_server_key(
|
||||||
|
server_id, tg_id, client_id, email, expiry_time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
await update_key_on_all_servers()
|
await update_key_on_all_servers()
|
||||||
|
|
||||||
await update_key_expiry(client_id, expiry_time)
|
await update_key_expiry(client_id, expiry_time)
|
||||||
|
|
||||||
response_message = (
|
response_message = f"Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
||||||
f"Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
|
||||||
)
|
|
||||||
|
|
||||||
back_button = InlineKeyboardButton(text="Назад", callback_data="back_to_user_editor")
|
back_button = InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="back_to_user_editor"
|
||||||
|
)
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await message.reply(response_message, reply_markup=keyboard, parse_mode="HTML")
|
await message.reply(
|
||||||
|
response_message, reply_markup=keyboard, parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
@@ -302,51 +377,87 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext):
|
|||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
async def renew_server_key(server_id, tg_id, client_id, email, new_expiry_time):
|
async def renew_server_key(server_id, tg_id, client_id, email, new_expiry_time):
|
||||||
try:
|
try:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
await extend_client_key_admin(session, server_id, tg_id, client_id, email, new_expiry_time)
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
|
await extend_client_key_admin(
|
||||||
|
session, server_id, tg_id, client_id, email, new_expiry_time
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Не удалось обновить ключ {client_id} на сервере {server_id}: {e}")
|
logger.error(
|
||||||
|
f"Не удалось обновить ключ {client_id} на сервере {server_id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('delete_key_admin|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("delete_key_admin|"))
|
||||||
async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
email = callback_query.data.split('|')[1]
|
email = callback_query.data.split("|")[1]
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
client_id = await conn.fetchval('SELECT client_id FROM keys WHERE email = $1', email)
|
client_id = await conn.fetchval(
|
||||||
|
"SELECT client_id FROM keys WHERE email = $1", email
|
||||||
|
)
|
||||||
|
|
||||||
if client_id is None:
|
if client_id is None:
|
||||||
await bot.edit_message_text("Ключ не найден.", chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.edit_message_text(
|
||||||
|
"Ключ не найден.",
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
confirmation_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
confirmation_keyboard = types.InlineKeyboardMarkup(
|
||||||
[types.InlineKeyboardButton(text='✅ Да, удалить', callback_data=f'confirm_delete_admin|{client_id}')],
|
inline_keyboard=[
|
||||||
[types.InlineKeyboardButton(text='❌ Нет, отменить', callback_data='view_keys')]
|
[
|
||||||
])
|
types.InlineKeyboardButton(
|
||||||
|
text="✅ Да, удалить",
|
||||||
|
callback_data=f"confirm_delete_admin|{client_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="❌ Нет, отменить", callback_data="view_keys"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
await bot.edit_message_text("<b>Вы уверены, что хотите удалить ключ?</b>", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=confirmation_keyboard, parse_mode="HTML")
|
await bot.edit_message_text(
|
||||||
|
"<b>Вы уверены, что хотите удалить ключ?</b>",
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
reply_markup=confirmation_keyboard,
|
||||||
|
parse_mode="HTML",
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('confirm_delete_admin|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("confirm_delete_admin|"))
|
||||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
client_id = callback_query.data.split('|')[1]
|
client_id = callback_query.data.split("|")[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('SELECT email FROM keys WHERE client_id = $1', client_id)
|
record = await conn.fetchrow(
|
||||||
|
"SELECT email FROM keys WHERE client_id = $1", client_id
|
||||||
|
)
|
||||||
|
|
||||||
if record:
|
if record:
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
response_message = "Ключ успешно удален."
|
response_message = "Ключ успешно удален."
|
||||||
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="view_keys"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
async def delete_key_from_servers():
|
async def delete_key_from_servers():
|
||||||
@@ -355,50 +466,78 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
|||||||
tasks.append(delete_key_from_server(server_id, client_id))
|
tasks.append(delete_key_from_server(server_id, client_id))
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
await delete_key_from_servers()
|
await delete_key_from_servers()
|
||||||
await delete_key_from_db(client_id)
|
await delete_key_from_db(client_id)
|
||||||
|
|
||||||
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard)
|
await bot.edit_message_text(
|
||||||
|
response_message,
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
response_message = "Ключ не найден или уже удален."
|
response_message = "Ключ не найден или уже удален."
|
||||||
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="view_keys"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard)
|
await bot.edit_message_text(
|
||||||
|
response_message,
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.edit_message_text(f"Ошибка при удалении ключа: {e}", chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.edit_message_text(
|
||||||
|
f"Ошибка при удалении ключа: {e}",
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
async def delete_key_from_server(server_id, client_id):
|
async def delete_key_from_server(server_id, client_id):
|
||||||
"""Удаление ключа с сервера"""
|
"""Удаление ключа с сервера"""
|
||||||
try:
|
try:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
success = await delete_client(session, server_id, client_id)
|
success = await delete_client(session, server_id, client_id)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
logger.error(f"Ошибка удаления ключа {client_id} на сервере {server_id}")
|
logger.error(f"Ошибка удаления ключа {client_id} на сервере {server_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении ключа {client_id} с сервера {server_id}: {e}")
|
logger.error(
|
||||||
|
f"Ошибка при удалении ключа {client_id} с сервера {server_id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def delete_key_from_db(client_id):
|
async def delete_key_from_db(client_id):
|
||||||
"""Удаление ключа из базы данных"""
|
"""Удаление ключа из базы данных"""
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id)
|
await conn.execute("DELETE FROM keys WHERE client_id = $1", client_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == "back_to_user_editor")
|
@router.callback_query(lambda c: c.data == "back_to_user_editor")
|
||||||
async def back_to_user_editor(callback_query: CallbackQuery):
|
async def back_to_user_editor(callback_query: CallbackQuery):
|
||||||
await back_to_admin_menu(callback_query)
|
await back_to_admin_menu(callback_query)
|
||||||
|
|
||||||
async def handle_error(tg_id, callback_query, message):
|
|
||||||
await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML")
|
|
||||||
|
|
||||||
|
async def handle_error(tg_id, callback_query, message):
|
||||||
|
await bot.edit_message_text(
|
||||||
|
message,
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
parse_mode="HTML",
|
||||||
|
)
|
||||||
|
|||||||
+60
-28
@@ -1,3 +1,5 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
import asyncpg
|
import asyncpg
|
||||||
from aiogram import F, Router, types
|
from aiogram import F, Router, types
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
@@ -9,44 +11,55 @@ from bot import bot
|
|||||||
from config import ADMIN_ID, DATABASE_URL
|
from config import ADMIN_ID, DATABASE_URL
|
||||||
from handlers.admin.admin import cmd_add_balance
|
from handlers.admin.admin import cmd_add_balance
|
||||||
from handlers.keys.key_management import handle_key_name_input
|
from handlers.keys.key_management import handle_key_name_input
|
||||||
from handlers.payment.pay import (ReplenishBalanceState,
|
from handlers.payment.yookassa_pay import (
|
||||||
process_custom_amount_input)
|
ReplenishBalanceState,
|
||||||
|
process_custom_amount_input,
|
||||||
|
)
|
||||||
from handlers.profile import process_callback_view_profile
|
from handlers.profile import process_callback_view_profile
|
||||||
from handlers.start import start_command
|
from handlers.start import start_command
|
||||||
from handlers.texts import TRIAL
|
from handlers.texts import TRIAL
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
class Form(StatesGroup):
|
class Form(StatesGroup):
|
||||||
waiting_for_server_selection = State()
|
waiting_for_server_selection = State()
|
||||||
waiting_for_key_name = State()
|
waiting_for_key_name = State()
|
||||||
viewing_profile = State()
|
viewing_profile = State()
|
||||||
waiting_for_message = State()
|
waiting_for_message = State()
|
||||||
|
|
||||||
@router.message(Command('backup'))
|
|
||||||
|
@router.message(Command("backup"))
|
||||||
async def backup_command(message: Message):
|
async def backup_command(message: Message):
|
||||||
if message.from_user.id != ADMIN_ID:
|
if message.from_user.id != ADMIN_ID:
|
||||||
await message.answer("У вас нет прав для выполнения этой команды.")
|
await message.answer("У вас нет прав для выполнения этой команды.")
|
||||||
return
|
return
|
||||||
|
|
||||||
from backup import backup_database
|
from backup import backup_database
|
||||||
|
|
||||||
await message.answer("Запускаю бэкап базы данных...")
|
await message.answer("Запускаю бэкап базы данных...")
|
||||||
await backup_database()
|
await backup_database()
|
||||||
await message.answer("Бэкап завершен и отправлен админу.")
|
await message.answer("Бэкап завершен и отправлен админу.")
|
||||||
|
|
||||||
@router.message(Command('start'))
|
|
||||||
|
@router.message(Command("start"))
|
||||||
async def handle_start(message: types.Message, state: FSMContext):
|
async def handle_start(message: types.Message, state: FSMContext):
|
||||||
await start_command(message)
|
await start_command(message)
|
||||||
|
|
||||||
@router.message(Command('add_balance'))
|
|
||||||
|
@router.message(Command("add_balance"))
|
||||||
async def handle_add_balance(message: types.Message, state: FSMContext):
|
async def handle_add_balance(message: types.Message, state: FSMContext):
|
||||||
await cmd_add_balance(message)
|
await cmd_add_balance(message)
|
||||||
|
|
||||||
@router.message(Command('menu'))
|
|
||||||
|
@router.message(Command("menu"))
|
||||||
async def handle_menu(message: types.Message, state: FSMContext):
|
async def handle_menu(message: types.Message, state: FSMContext):
|
||||||
await start_command(message)
|
await start_command(message)
|
||||||
|
|
||||||
@router.message(Command('send_trial'))
|
|
||||||
|
@router.message(Command("send_trial"))
|
||||||
async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
||||||
# Проверка на администратора
|
# Проверка на администратора
|
||||||
if message.from_user.id != ADMIN_ID:
|
if message.from_user.id != ADMIN_ID:
|
||||||
@@ -56,25 +69,35 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
|||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
records = await conn.fetch('''
|
records = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT tg_id FROM connections WHERE trial = 0
|
SELECT tg_id FROM connections WHERE trial = 0
|
||||||
''')
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
if records:
|
if records:
|
||||||
for record in records:
|
for record in records:
|
||||||
tg_id = record['tg_id']
|
tg_id = record["tg_id"]
|
||||||
trial_message = TRIAL
|
trial_message = TRIAL
|
||||||
try:
|
try:
|
||||||
await bot.send_message(chat_id=tg_id, text=trial_message)
|
await bot.send_message(chat_id=tg_id, text=trial_message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "Forbidden: bot was blocked by the user" in str(e):
|
if "Forbidden: bot was blocked by the user" in str(e):
|
||||||
print(f"Бот заблокирован пользователем с tg_id: {tg_id}")
|
logging.info(
|
||||||
|
f"Бот заблокирован пользователем с tg_id: {tg_id}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f"Ошибка при отправке сообщения пользователю {tg_id}: {e}")
|
logging.error(
|
||||||
|
f"Ошибка при отправке сообщения пользователю {tg_id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
await message.answer("Сообщения о пробном периоде отправлены всем пользователям с не использованным ключом.")
|
await message.answer(
|
||||||
|
"Сообщения о пробном периоде отправлены всем пользователям с не использованным ключом."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await message.answer("Нет пользователей с не использованными пробными ключами.")
|
await message.answer(
|
||||||
|
"Нет пользователей с не использованными пробными ключами."
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
@@ -82,54 +105,63 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.answer(f"Ошибка при отправке сообщений: {e}")
|
await message.answer(f"Ошибка при отправке сообщений: {e}")
|
||||||
|
|
||||||
@router.message(Command('send_to_all'))
|
|
||||||
async def send_message_to_all_clients(message: types.Message, state: FSMContext, from_panel=False):
|
@router.message(Command("send_to_all"))
|
||||||
|
async def send_message_to_all_clients(
|
||||||
|
message: types.Message, state: FSMContext, from_panel=False
|
||||||
|
):
|
||||||
if not from_panel and message.from_user.id != ADMIN_ID:
|
if not from_panel and message.from_user.id != ADMIN_ID:
|
||||||
await message.answer("У вас нет прав для выполнения этой команды.")
|
await message.answer("У вас нет прав для выполнения этой команды.")
|
||||||
return
|
return
|
||||||
|
|
||||||
await message.answer("Введите текст сообщения, который вы хотите отправить всем клиентам:")
|
await message.answer(
|
||||||
await state.set_state(Form.waiting_for_message)
|
"Введите текст сообщения, который вы хотите отправить всем клиентам:"
|
||||||
|
)
|
||||||
|
await state.set_state(Form.waiting_for_message)
|
||||||
|
|
||||||
|
|
||||||
@router.message(Form.waiting_for_message)
|
@router.message(Form.waiting_for_message)
|
||||||
async def process_message_to_all(message: types.Message, state: FSMContext):
|
async def process_message_to_all(message: types.Message, state: FSMContext):
|
||||||
text_message = message.text
|
text_message = message.text
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
tg_ids = await conn.fetch('SELECT tg_id FROM connections')
|
tg_ids = await conn.fetch("SELECT tg_id FROM connections")
|
||||||
|
|
||||||
for record in tg_ids:
|
for record in tg_ids:
|
||||||
tg_id = record['tg_id']
|
tg_id = record["tg_id"]
|
||||||
try:
|
try:
|
||||||
await bot.send_message(chat_id=tg_id, text=text_message)
|
await bot.send_message(chat_id=tg_id, text=text_message)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при отправке сообщения пользователю {tg_id}: {e}. Пропускаем этого пользователя.")
|
logging.error(
|
||||||
|
f"Ошибка при отправке сообщения пользователю {tg_id}: {e}. Пропускаем этого пользователя."
|
||||||
|
)
|
||||||
|
|
||||||
await message.answer("Сообщение было отправлено всем клиентам.")
|
await message.answer("Сообщение было отправлено всем клиентам.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при подключении к базе данных: {e}")
|
logging.error(f"Ошибка при подключении к базе данных: {e}")
|
||||||
await message.answer("Произошла ошибка при отправке сообщения.")
|
await message.answer("Произошла ошибка при отправке сообщения.")
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
@router.message()
|
@router.message()
|
||||||
async def handle_text(message: types.Message, state: FSMContext):
|
async def handle_text(message: types.Message, state: FSMContext):
|
||||||
current_state = await state.get_state()
|
current_state = await state.get_state()
|
||||||
|
|
||||||
if message.text in ["/send_to_all"]:
|
if message.text in ["/send_to_all"]:
|
||||||
await send_message_to_all_clients(message, state)
|
await send_message_to_all_clients(message, state)
|
||||||
return
|
return
|
||||||
|
|
||||||
if message.text == "Мой профиль":
|
if message.text == "Мой профиль":
|
||||||
callback_query = types.CallbackQuery(
|
callback_query = types.CallbackQuery(
|
||||||
id="1",
|
id="1",
|
||||||
from_user=message.from_user,
|
from_user=message.from_user,
|
||||||
chat_instance='',
|
chat_instance="",
|
||||||
data='view_profile',
|
data="view_profile",
|
||||||
message=message
|
message=message,
|
||||||
)
|
)
|
||||||
await process_callback_view_profile(callback_query, state)
|
await process_callback_view_profile(callback_query, state)
|
||||||
return
|
return
|
||||||
@@ -146,5 +178,5 @@ async def handle_text(message: types.Message, state: FSMContext):
|
|||||||
await backup_command(message)
|
await backup_command(message)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif current_state is None:
|
elif current_state is None:
|
||||||
await start_command(message)
|
await start_command(message)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from aiogram.types import (BufferedInputFile, InlineKeyboardButton,
|
from aiogram.types import BufferedInputFile, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
InlineKeyboardMarkup)
|
|
||||||
|
|
||||||
from handlers.texts import INSTRUCTIONS
|
from handlers.texts import INSTRUCTIONS
|
||||||
|
|
||||||
@@ -10,26 +9,24 @@ from handlers.texts import INSTRUCTIONS
|
|||||||
async def send_instructions(callback_query: types.CallbackQuery):
|
async def send_instructions(callback_query: types.CallbackQuery):
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
|
|
||||||
instructions_message = (
|
instructions_message = INSTRUCTIONS
|
||||||
INSTRUCTIONS
|
|
||||||
)
|
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), 'instructions.jpg')
|
image_path = os.path.join(os.path.dirname(__file__), "instructions.jpg")
|
||||||
|
|
||||||
if not os.path.isfile(image_path):
|
if not os.path.isfile(image_path):
|
||||||
await callback_query.message.answer("Файл изображения не найден.")
|
await callback_query.message.answer("Файл изображения не найден.")
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
return
|
return
|
||||||
|
|
||||||
back_button = InlineKeyboardButton(text='🔙 Назад', callback_data='back_to_main')
|
back_button = InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
with open(image_path, 'rb') as image_from_buffer:
|
with open(image_path, "rb") as image_from_buffer:
|
||||||
await callback_query.message.answer_photo(
|
await callback_query.message.answer_photo(
|
||||||
BufferedInputFile(image_from_buffer.read(), filename="instructions.jpg"),
|
BufferedInputFile(image_from_buffer.read(), filename="instructions.jpg"),
|
||||||
caption=instructions_message,
|
caption=instructions_message,
|
||||||
parse_mode='Markdown',
|
parse_mode="Markdown",
|
||||||
reply_markup=keyboard
|
reply_markup=keyboard,
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|||||||
+135
-60
@@ -7,14 +7,24 @@ import asyncpg
|
|||||||
from aiogram import F, Router
|
from aiogram import F, Router
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
from aiogram.types import (CallbackQuery, InlineKeyboardButton,
|
from aiogram.types import (
|
||||||
InlineKeyboardMarkup, Message)
|
CallbackQuery,
|
||||||
|
InlineKeyboardButton,
|
||||||
|
InlineKeyboardMarkup,
|
||||||
|
Message,
|
||||||
|
)
|
||||||
|
|
||||||
from auth import login_with_credentials
|
from auth import login_with_credentials
|
||||||
from bot import bot, dp
|
from bot import bot, dp
|
||||||
from client import add_client
|
from client import add_client
|
||||||
from config import (ADMIN_PASSWORD, ADMIN_USERNAME, APP_URL, DATABASE_URL,
|
from config import (
|
||||||
PUBLIC_LINK, SERVERS)
|
ADMIN_PASSWORD,
|
||||||
|
ADMIN_USERNAME,
|
||||||
|
APP_URL,
|
||||||
|
DATABASE_URL,
|
||||||
|
PUBLIC_LINK,
|
||||||
|
SERVERS,
|
||||||
|
)
|
||||||
from database import add_connection, get_balance, store_key, update_balance
|
from database import add_connection, get_balance, store_key, update_balance
|
||||||
from handlers.instructions.instructions import send_instructions
|
from handlers.instructions.instructions import send_instructions
|
||||||
from handlers.profile import process_callback_view_profile
|
from handlers.profile import process_callback_view_profile
|
||||||
@@ -23,23 +33,29 @@ from handlers.utils import sanitize_key_name
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Form(StatesGroup):
|
class Form(StatesGroup):
|
||||||
waiting_for_server_selection = State()
|
waiting_for_server_selection = State()
|
||||||
waiting_for_key_name = State()
|
waiting_for_key_name = State()
|
||||||
viewing_profile = State()
|
viewing_profile = State()
|
||||||
waiting_for_message = State()
|
waiting_for_message = State()
|
||||||
|
|
||||||
@dp.callback_query(F.data == 'create_key')
|
|
||||||
|
@dp.callback_query(F.data == "create_key")
|
||||||
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
|
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
server_id = "все сервера"
|
server_id = "все сервера"
|
||||||
await state.update_data(selected_server_id=server_id)
|
await state.update_data(selected_server_id=server_id)
|
||||||
@@ -47,81 +63,102 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC
|
|||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
async def select_server(callback_query: CallbackQuery, state: FSMContext):
|
||||||
selected_server_id = (await state.get_data()).get("selected_server_id")
|
selected_server_id = (await state.get_data()).get("selected_server_id")
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', callback_query.from_user.id)
|
existing_connection = await conn.fetchrow(
|
||||||
|
"SELECT trial FROM connections WHERE tg_id = $1",
|
||||||
|
callback_query.from_user.id,
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
trial_status = existing_connection['trial'] if existing_connection else 0
|
trial_status = existing_connection["trial"] if existing_connection else 0
|
||||||
|
|
||||||
if trial_status == 1:
|
if trial_status == 1:
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=callback_query.from_user.id,
|
chat_id=callback_query.from_user.id,
|
||||||
text=KEY,
|
text=KEY,
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
reply_markup=InlineKeyboardMarkup(
|
||||||
[InlineKeyboardButton(text='✅ Да, подключить новое устройство', callback_data='confirm_create_new_key')],
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text='↩️ Назад', callback_data='cancel_create_key')]
|
[
|
||||||
])
|
InlineKeyboardButton(
|
||||||
|
text="✅ Да, подключить новое устройство",
|
||||||
|
callback_data="confirm_create_new_key",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="↩️ Назад", callback_data="cancel_create_key"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
)
|
)
|
||||||
await state.update_data(creating_new_key=True)
|
await state.update_data(creating_new_key=True)
|
||||||
else:
|
else:
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=callback_query.from_user.id,
|
chat_id=callback_query.from_user.id, text=KEY_TRIAL, parse_mode="HTML"
|
||||||
text=KEY_TRIAL,
|
|
||||||
parse_mode="HTML"
|
|
||||||
)
|
)
|
||||||
await state.set_state(Form.waiting_for_key_name)
|
await state.set_state(Form.waiting_for_key_name)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(F.data == 'confirm_create_new_key')
|
@dp.callback_query(F.data == "confirm_create_new_key")
|
||||||
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext):
|
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
server_id = data.get('selected_server_id')
|
server_id = data.get("selected_server_id")
|
||||||
|
|
||||||
balance = await get_balance(tg_id)
|
balance = await get_balance(tg_id)
|
||||||
if balance < 100:
|
if balance < 100:
|
||||||
replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')
|
replenish_button = InlineKeyboardButton(
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
text="Перейти в профиль", callback_data="view_profile"
|
||||||
await callback_query.message.edit_text(
|
|
||||||
NULL_BALANCE,
|
|
||||||
reply_markup=keyboard
|
|
||||||
)
|
)
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||||
|
await callback_query.message.edit_text(NULL_BALANCE, reply_markup=keyboard)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
await callback_query.message.edit_text("🔑 Пожалуйста, введите имя подключаемого устройства:")
|
await callback_query.message.edit_text(
|
||||||
|
"🔑 Пожалуйста, введите имя подключаемого устройства:"
|
||||||
|
)
|
||||||
await state.set_state(Form.waiting_for_key_name)
|
await state.set_state(Form.waiting_for_key_name)
|
||||||
await state.update_data(creating_new_key=True)
|
await state.update_data(creating_new_key=True)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@dp.callback_query(F.data == 'cancel_create_key')
|
|
||||||
|
@dp.callback_query(F.data == "cancel_create_key")
|
||||||
async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext):
|
async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext):
|
||||||
await process_callback_view_profile(callback_query, state)
|
await process_callback_view_profile(callback_query, state)
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
async def handle_key_name_input(message: Message, state: FSMContext):
|
async def handle_key_name_input(message: Message, state: FSMContext):
|
||||||
tg_id = message.from_user.id
|
tg_id = message.from_user.id
|
||||||
key_name = sanitize_key_name(message.text)
|
key_name = sanitize_key_name(message.text)
|
||||||
|
|
||||||
if not key_name:
|
if not key_name:
|
||||||
await message.bot.send_message(tg_id, "📝 Пожалуйста, назовите устройство на английском языке.")
|
await message.bot.send_message(
|
||||||
|
tg_id, "📝 Пожалуйста, назовите устройство на английском языке."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
existing_key = await conn.fetchrow('SELECT * FROM keys WHERE email = $1', key_name.lower())
|
existing_key = await conn.fetchrow(
|
||||||
|
"SELECT * FROM keys WHERE email = $1", key_name.lower()
|
||||||
|
)
|
||||||
if existing_key:
|
if existing_key:
|
||||||
await message.bot.send_message(tg_id, "❌ Это имя уже используется. Пожалуйста, выберите другое имя для ключа.")
|
await message.bot.send_message(
|
||||||
|
tg_id,
|
||||||
|
"❌ Это имя уже используется. Пожалуйста, выберите другое имя для ключа.",
|
||||||
|
)
|
||||||
await state.set_state(Form.waiting_for_key_name)
|
await state.set_state(Form.waiting_for_key_name)
|
||||||
return
|
return
|
||||||
finally:
|
finally:
|
||||||
@@ -135,20 +172,28 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
|||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', tg_id)
|
existing_connection = await conn.fetchrow(
|
||||||
|
"SELECT trial FROM connections WHERE tg_id = $1", tg_id
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
trial_status = existing_connection['trial'] if existing_connection else 0
|
trial_status = existing_connection["trial"] if existing_connection else 0
|
||||||
|
|
||||||
if trial_status == 0:
|
if trial_status == 0:
|
||||||
expiry_time = current_time + timedelta(days=1, hours=3)
|
expiry_time = current_time + timedelta(days=1, hours=3)
|
||||||
else:
|
else:
|
||||||
balance = await get_balance(tg_id)
|
balance = await get_balance(tg_id)
|
||||||
if balance < 100:
|
if balance < 100:
|
||||||
replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')
|
replenish_button = InlineKeyboardButton(
|
||||||
|
text="Перейти в профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
|
||||||
await message.bot.send_message(tg_id, "❗️ Недостаточно средств на балансе для создания подписки на новое устройство.", reply_markup=keyboard)
|
await message.bot.send_message(
|
||||||
|
tg_id,
|
||||||
|
"❗️ Недостаточно средств на балансе для создания подписки на новое устройство.",
|
||||||
|
reply_markup=keyboard,
|
||||||
|
)
|
||||||
await state.clear()
|
await state.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -158,67 +203,96 @@ async def handle_key_name_input(message: Message, state: FSMContext):
|
|||||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||||
public_link = f"{PUBLIC_LINK}{email}"
|
public_link = f"{PUBLIC_LINK}{email}"
|
||||||
|
|
||||||
button_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')
|
button_profile = InlineKeyboardButton(
|
||||||
|
text="👤 Мой профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
button_iphone = InlineKeyboardButton(
|
button_iphone = InlineKeyboardButton(
|
||||||
text='🍏 Подключить',
|
text="🍏 Подключить", url=f"{APP_URL}/?url=v2raytun://import/{public_link}"
|
||||||
url=f'{APP_URL}/?url=v2raytun://import/{public_link}'
|
|
||||||
)
|
)
|
||||||
button_android = InlineKeyboardButton(
|
button_android = InlineKeyboardButton(
|
||||||
text='🤖 Подключить',
|
text="🤖 Подключить",
|
||||||
url=f'{APP_URL}/?url=v2raytun://import-sub?url={public_link}'
|
url=f"{APP_URL}/?url=v2raytun://import-sub?url={public_link}",
|
||||||
)
|
)
|
||||||
|
|
||||||
button_download_ios = InlineKeyboardButton(
|
button_download_ios = InlineKeyboardButton(
|
||||||
text='🍏 Скачать',
|
text="🍏 Скачать", url="https://apps.apple.com/ru/app/v2raytun/id6476628951"
|
||||||
url="https://apps.apple.com/ru/app/v2raytun/id6476628951"
|
|
||||||
)
|
)
|
||||||
button_download_android = InlineKeyboardButton(
|
button_download_android = InlineKeyboardButton(
|
||||||
text='🤖 Скачать',
|
text="🤖 Скачать",
|
||||||
url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru"
|
url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru",
|
||||||
)
|
)
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(
|
||||||
[button_download_ios, button_download_android],
|
inline_keyboard=[
|
||||||
[button_iphone, button_android],
|
[button_download_ios, button_download_android],
|
||||||
[button_profile]
|
[button_iphone, button_android],
|
||||||
])
|
[button_profile],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
remaining_time = expiry_time - current_time
|
remaining_time = expiry_time - current_time
|
||||||
days = remaining_time.days
|
days = remaining_time.days
|
||||||
key_message = key_message_success(public_link, f"Оставшееся время ключа: {days} день")
|
key_message = key_message_success(
|
||||||
|
public_link, f"Оставшееся время ключа: {days} день"
|
||||||
|
)
|
||||||
|
|
||||||
await message.bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard)
|
await message.bot.send_message(
|
||||||
|
tg_id, key_message, parse_mode="HTML", reply_markup=keyboard
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tasks = []
|
tasks = []
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
tasks.append(asyncio.create_task(create_key_on_server(server_id, tg_id, client_id, email, expiry_timestamp)))
|
tasks.append(
|
||||||
|
asyncio.create_task(
|
||||||
|
create_key_on_server(
|
||||||
|
server_id, tg_id, client_id, email, expiry_timestamp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
existing_connection = await conn.fetchrow('SELECT * FROM connections WHERE tg_id = $1', tg_id)
|
existing_connection = await conn.fetchrow(
|
||||||
|
"SELECT * FROM connections WHERE tg_id = $1", tg_id
|
||||||
|
)
|
||||||
if existing_connection:
|
if existing_connection:
|
||||||
await conn.execute('UPDATE connections SET trial = 1 WHERE tg_id = $1', tg_id)
|
await conn.execute(
|
||||||
|
"UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await add_connection(tg_id, 0, 1)
|
await add_connection(tg_id, 0, 1)
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
await store_key(tg_id, client_id, email, expiry_timestamp, public_link, 'all_servers')
|
await store_key(
|
||||||
|
tg_id, client_id, email, expiry_timestamp, public_link, "all_servers"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.bot.send_message(tg_id, f"❌ Ошибка при создании ключа: {e}")
|
await message.bot.send_message(tg_id, f"❌ Ошибка при создании ключа: {e}")
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
async def create_key_on_server(server_id, tg_id, client_id, email, expiry_timestamp):
|
async def create_key_on_server(server_id, tg_id, client_id, email, expiry_timestamp):
|
||||||
try:
|
try:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
response = await add_client(
|
response = await add_client(
|
||||||
session, server_id, client_id, email, tg_id, limit_ip=1, total_gb=0,
|
session,
|
||||||
expiry_time=expiry_timestamp, enable=True, flow="xtls-rprx-vision"
|
server_id,
|
||||||
|
client_id,
|
||||||
|
email,
|
||||||
|
tg_id,
|
||||||
|
limit_ip=1,
|
||||||
|
total_gb=0,
|
||||||
|
expiry_time=expiry_timestamp,
|
||||||
|
enable=True,
|
||||||
|
flow="xtls-rprx-vision",
|
||||||
)
|
)
|
||||||
if not response.get("success", True):
|
if not response.get("success", True):
|
||||||
error_msg = response.get("msg", "Неизвестная ошибка.")
|
error_msg = response.get("msg", "Неизвестная ошибка.")
|
||||||
@@ -230,11 +304,12 @@ async def create_key_on_server(server_id, tg_id, client_id, email, expiry_timest
|
|||||||
logger.error(f"Ошибка на сервере {server_id}: {e}")
|
logger.error(f"Ошибка на сервере {server_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(F.data == 'instructions')
|
@dp.callback_query(F.data == "instructions")
|
||||||
async def handle_instructions(callback_query: CallbackQuery):
|
async def handle_instructions(callback_query: CallbackQuery):
|
||||||
await send_instructions(callback_query)
|
await send_instructions(callback_query)
|
||||||
|
|
||||||
@dp.callback_query(F.data == 'back_to_main')
|
|
||||||
|
@dp.callback_query(F.data == "back_to_main")
|
||||||
async def handle_back_to_main(callback_query: CallbackQuery, state: FSMContext):
|
async def handle_back_to_main(callback_query: CallbackQuery, state: FSMContext):
|
||||||
await process_callback_view_profile(callback_query, state)
|
await process_callback_view_profile(callback_query, state)
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|||||||
+387
-154
@@ -11,41 +11,69 @@ from aiogram.types import BufferedInputFile
|
|||||||
from auth import login_with_credentials
|
from auth import login_with_credentials
|
||||||
from bot import bot
|
from bot import bot
|
||||||
from client import add_client, delete_client, extend_client_key
|
from client import add_client, delete_client, extend_client_key
|
||||||
from config import (ADMIN_PASSWORD, ADMIN_USERNAME, APP_URL, DATABASE_URL,
|
from config import (
|
||||||
PUBLIC_LINK, SERVERS)
|
ADMIN_PASSWORD,
|
||||||
from database import (delete_key, get_balance, store_key, update_balance,
|
ADMIN_USERNAME,
|
||||||
update_key_expiry)
|
APP_URL,
|
||||||
from handlers.texts import (INSUFFICIENT_FUNDS_MSG, KEY_NOT_FOUND_MSG, NO_KEYS,
|
DATABASE_URL,
|
||||||
PLAN_SELECTION_MSG, RENEWAL_PLANS,
|
PUBLIC_LINK,
|
||||||
SUCCESS_RENEWAL_MSG, key_message)
|
SERVERS,
|
||||||
|
)
|
||||||
|
from database import (
|
||||||
|
delete_key,
|
||||||
|
get_balance,
|
||||||
|
store_key,
|
||||||
|
update_balance,
|
||||||
|
update_key_expiry,
|
||||||
|
)
|
||||||
|
from handlers.texts import (
|
||||||
|
INSUFFICIENT_FUNDS_MSG,
|
||||||
|
KEY_NOT_FOUND_MSG,
|
||||||
|
NO_KEYS,
|
||||||
|
PLAN_SELECTION_MSG,
|
||||||
|
RENEWAL_PLANS,
|
||||||
|
SUCCESS_RENEWAL_MSG,
|
||||||
|
key_message,
|
||||||
|
)
|
||||||
|
|
||||||
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
|
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'view_keys')
|
|
||||||
|
@router.callback_query(lambda c: c.data == "view_keys")
|
||||||
async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
records = await conn.fetch('''
|
records = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT email, client_id FROM keys WHERE tg_id = $1
|
SELECT email, client_id FROM keys WHERE tg_id = $1
|
||||||
''', tg_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
)
|
||||||
|
|
||||||
if records:
|
if records:
|
||||||
buttons = []
|
buttons = []
|
||||||
for record in records:
|
for record in records:
|
||||||
key_name = record['email']
|
key_name = record["email"]
|
||||||
client_id = record['client_id']
|
client_id = record["client_id"]
|
||||||
button = types.InlineKeyboardButton(text=f"🔑 {key_name}", callback_data=f'view_key|{key_name}|{client_id}')
|
button = types.InlineKeyboardButton(
|
||||||
|
text=f"🔑 {key_name}",
|
||||||
|
callback_data=f"view_key|{key_name}|{client_id}",
|
||||||
|
)
|
||||||
buttons.append([button])
|
buttons.append([button])
|
||||||
|
|
||||||
back_button = types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile')
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="🔙 Назад", callback_data="view_profile"
|
||||||
|
)
|
||||||
buttons.append([back_button])
|
buttons.append([back_button])
|
||||||
|
|
||||||
inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons)
|
inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||||
@@ -54,28 +82,38 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
|||||||
"<i>Нажмите на имя устройства для управления его подпиской.</i>"
|
"<i>Нажмите на имя устройства для управления его подпиской.</i>"
|
||||||
)
|
)
|
||||||
|
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id,
|
||||||
text=response_message,
|
text=response_message,
|
||||||
reply_markup=inline_keyboard,
|
reply_markup=inline_keyboard,
|
||||||
parse_mode="HTML"
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
response_message = NO_KEYS
|
response_message = NO_KEYS
|
||||||
create_key_button = types.InlineKeyboardButton(text='➕ Создать ключ', callback_data='create_key')
|
create_key_button = types.InlineKeyboardButton(
|
||||||
back_button = types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile')
|
text="➕ Создать ключ", callback_data="create_key"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button], [back_button]])
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="🔙 Назад", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
keyboard = types.InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[[create_key_button], [back_button]]
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id,
|
||||||
text=response_message,
|
text=response_message,
|
||||||
reply_markup=keyboard,
|
reply_markup=keyboard,
|
||||||
parse_mode="HTML"
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
@@ -86,31 +124,41 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
|
|||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('view_key|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("view_key|"))
|
||||||
async def process_callback_view_key(callback_query: types.CallbackQuery):
|
async def process_callback_view_key(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
key_name, client_id = callback_query.data.split('|')[1], callback_query.data.split('|')[2]
|
key_name, client_id = (
|
||||||
|
callback_query.data.split("|")[1],
|
||||||
|
callback_query.data.split("|")[2],
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('''
|
record = await conn.fetchrow(
|
||||||
|
"""
|
||||||
SELECT k.expiry_time, k.server_id, k.key
|
SELECT k.expiry_time, k.server_id, k.key
|
||||||
FROM keys k
|
FROM keys k
|
||||||
WHERE k.tg_id = $1 AND k.email = $2
|
WHERE k.tg_id = $1 AND k.email = $2
|
||||||
''', tg_id, key_name)
|
""",
|
||||||
|
tg_id,
|
||||||
|
key_name,
|
||||||
|
)
|
||||||
|
|
||||||
if record:
|
if record:
|
||||||
key = record['key']
|
key = record["key"]
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
server_id = record['server_id']
|
server_id = record["server_id"]
|
||||||
|
|
||||||
server_name = SERVERS.get(server_id, {}).get('name', 'мультисервер')
|
server_name = SERVERS.get(server_id, {}).get("name", "мультисервер")
|
||||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||||
current_date = datetime.utcnow()
|
current_date = datetime.utcnow()
|
||||||
time_left = expiry_date - current_date
|
time_left = expiry_date - current_date
|
||||||
@@ -123,259 +171,387 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
|
|||||||
hours_left = time_left.seconds // 3600
|
hours_left = time_left.seconds // 3600
|
||||||
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
||||||
|
|
||||||
formatted_expiry_date = expiry_date.strftime('%d %B %Y года')
|
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||||
response_message = key_message(key, formatted_expiry_date, days_left_message, server_name)
|
response_message = key_message(
|
||||||
|
key, formatted_expiry_date, days_left_message, server_name
|
||||||
|
)
|
||||||
|
|
||||||
download_android_button = types.InlineKeyboardButton(
|
download_android_button = types.InlineKeyboardButton(
|
||||||
text='🤖 Скачать',
|
text="🤖 Скачать",
|
||||||
url='https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru'
|
url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru",
|
||||||
)
|
)
|
||||||
download_iphone_button = types.InlineKeyboardButton(
|
download_iphone_button = types.InlineKeyboardButton(
|
||||||
text='🍏 Скачать',
|
text="🍏 Скачать",
|
||||||
url='https://apps.apple.com/ru/app/v2raytun/id6476628951'
|
url="https://apps.apple.com/ru/app/v2raytun/id6476628951",
|
||||||
)
|
)
|
||||||
|
|
||||||
connect_iphone_button = types.InlineKeyboardButton(
|
connect_iphone_button = types.InlineKeyboardButton(
|
||||||
text='🍏 Подключить',
|
text="🍏 Подключить", url=f"{APP_URL}/?url=v2raytun://import/{key}"
|
||||||
url=f'{APP_URL}/?url=v2raytun://import/{key}'
|
|
||||||
)
|
)
|
||||||
connect_android_button = types.InlineKeyboardButton(
|
connect_android_button = types.InlineKeyboardButton(
|
||||||
text='🤖 Подключить',
|
text="🤖 Подключить",
|
||||||
url=f'{APP_URL}/?url=v2raytun://import-sub?url={key}'
|
url=f"{APP_URL}/?url=v2raytun://import-sub?url={key}",
|
||||||
)
|
)
|
||||||
|
|
||||||
renew_button = types.InlineKeyboardButton(text='⏳ Продлить', callback_data=f'renew_key|{client_id}')
|
renew_button = types.InlineKeyboardButton(
|
||||||
delete_button = types.InlineKeyboardButton(text='❌ Удалить', callback_data=f'delete_key|{client_id}')
|
text="⏳ Продлить", callback_data=f"renew_key|{client_id}"
|
||||||
back_button = types.InlineKeyboardButton(text='🔙 Назад в профиль', callback_data='view_profile')
|
)
|
||||||
|
delete_button = types.InlineKeyboardButton(
|
||||||
|
text="❌ Удалить", callback_data=f"delete_key|{client_id}"
|
||||||
|
)
|
||||||
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="🔙 Назад в профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
|
||||||
inline_keyboard = [
|
inline_keyboard = [
|
||||||
[download_iphone_button, download_android_button],
|
[download_iphone_button, download_android_button],
|
||||||
[connect_iphone_button, connect_android_button],
|
[connect_iphone_button, connect_android_button],
|
||||||
[renew_button, delete_button],
|
[renew_button, delete_button],
|
||||||
]
|
]
|
||||||
|
|
||||||
if not key.startswith(PUBLIC_LINK):
|
if not key.startswith(PUBLIC_LINK):
|
||||||
update_subscription_button = types.InlineKeyboardButton(text='🔄 Обновить подписку', callback_data=f'update_subscription|{client_id}')
|
update_subscription_button = types.InlineKeyboardButton(
|
||||||
inline_keyboard.append([update_subscription_button])
|
text="🔄 Обновить подписку",
|
||||||
|
callback_data=f"update_subscription|{client_id}",
|
||||||
|
)
|
||||||
|
inline_keyboard.append([update_subscription_button])
|
||||||
|
|
||||||
inline_keyboard.append([back_button])
|
inline_keyboard.append([back_button])
|
||||||
|
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=inline_keyboard)
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), 'pic_view.jpg')
|
image_path = os.path.join(os.path.dirname(__file__), "pic_view.jpg")
|
||||||
|
|
||||||
if not os.path.isfile(image_path):
|
if not os.path.isfile(image_path):
|
||||||
await bot.send_message(tg_id, "Файл изображения не найден.")
|
await bot.send_message(tg_id, "Файл изображения не найден.")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(image_path, 'rb') as image_file:
|
with open(image_path, "rb") as image_file:
|
||||||
await bot.send_photo(
|
await bot.send_photo(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id,
|
||||||
photo=BufferedInputFile(image_file.read(), filename="pic_view.jpg"),
|
photo=BufferedInputFile(
|
||||||
|
image_file.read(), filename="pic_view.jpg"
|
||||||
|
),
|
||||||
caption=response_message,
|
caption=response_message,
|
||||||
reply_markup=keyboard,
|
reply_markup=keyboard,
|
||||||
parse_mode="HTML"
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await bot.send_message(chat_id=tg_id, text="<b>Информация о подписке не найдена.</b>", parse_mode="HTML")
|
await bot.send_message(
|
||||||
|
chat_id=tg_id,
|
||||||
|
text="<b>Информация о подписке не найдена.</b>",
|
||||||
|
parse_mode="HTML",
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await handle_error(tg_id, callback_query, f"Ошибка при получении информации о ключе: {e}")
|
await handle_error(
|
||||||
|
tg_id, callback_query, f"Ошибка при получении информации о ключе: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('update_subscription|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("update_subscription|"))
|
||||||
async def process_callback_update_subscription(callback_query: types.CallbackQuery):
|
async def process_callback_update_subscription(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
client_id = callback_query.data.split('|')[1]
|
client_id = callback_query.data.split("|")[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('''
|
record = await conn.fetchrow(
|
||||||
|
"""
|
||||||
SELECT k.key, k.expiry_time, k.email, k.server_id
|
SELECT k.key, k.expiry_time, k.email, k.server_id
|
||||||
FROM keys k
|
FROM keys k
|
||||||
WHERE k.tg_id = $1 AND k.client_id = $2
|
WHERE k.tg_id = $1 AND k.client_id = $2
|
||||||
''', tg_id, client_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
client_id,
|
||||||
|
)
|
||||||
|
|
||||||
if record:
|
if record:
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
public_link = f"{PUBLIC_LINK}{email}"
|
public_link = f"{PUBLIC_LINK}{email}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await conn.execute('''
|
await conn.execute(
|
||||||
|
"""
|
||||||
DELETE FROM keys
|
DELETE FROM keys
|
||||||
WHERE tg_id = $1 AND client_id = $2
|
WHERE tg_id = $1 AND client_id = $2
|
||||||
''', tg_id, client_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
client_id,
|
||||||
|
)
|
||||||
except Exception as delete_error:
|
except Exception as delete_error:
|
||||||
await bot.send_message(tg_id, f"Ошибка при удалении старой подписки: {delete_error}")
|
await bot.send_message(
|
||||||
|
tg_id, f"Ошибка при удалении старой подписки: {delete_error}"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
tasks.append(update_key_on_server(tg_id, client_id, email, expiry_time, server_id))
|
tasks.append(
|
||||||
|
update_key_on_server(
|
||||||
|
tg_id, client_id, email, expiry_time, server_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
results = await asyncio.gather(*tasks)
|
results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
await store_key(tg_id, client_id, email, expiry_time, public_link, server_id='все сервера')
|
await store_key(
|
||||||
|
tg_id,
|
||||||
|
client_id,
|
||||||
|
email,
|
||||||
|
expiry_time,
|
||||||
|
public_link,
|
||||||
|
server_id="все сервера",
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||||
|
|
||||||
response_message = f"Ваша подписка {email} обновлена!"
|
response_message = f"Ваша подписка {email} обновлена!"
|
||||||
back_button = types.InlineKeyboardButton(text='🔙 Назад в профиль', callback_data='view_profile')
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="🔙 Назад в профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
tg_id,
|
tg_id, response_message, reply_markup=keyboard, parse_mode="HTML"
|
||||||
response_message,
|
|
||||||
reply_markup=keyboard,
|
|
||||||
parse_mode="HTML"
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении сообщения: {e}")
|
logger.error(f"Ошибка при удалении сообщения: {e}")
|
||||||
|
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
tg_id,
|
tg_id, "<b>Ключ не найден в базе данных.</b>", parse_mode="HTML"
|
||||||
"<b>Ключ не найден в базе данных.</b>",
|
|
||||||
parse_mode="HTML"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await handle_error(tg_id, callback_query, f"Ошибка при обновлении подписки: {e}")
|
await handle_error(
|
||||||
|
tg_id, callback_query, f"Ошибка при обновлении подписки: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
async def update_key_on_server(tg_id, client_id, email, expiry_time, server_id):
|
async def update_key_on_server(tg_id, client_id, email, expiry_time, server_id):
|
||||||
try:
|
try:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
response = await add_client(
|
response = await add_client(
|
||||||
session, server_id, client_id, email, tg_id,
|
session,
|
||||||
limit_ip=1, total_gb=0, expiry_time=expiry_time,
|
server_id,
|
||||||
enable=True, flow="xtls-rprx-vision"
|
client_id,
|
||||||
|
email,
|
||||||
|
tg_id,
|
||||||
|
limit_ip=1,
|
||||||
|
total_gb=0,
|
||||||
|
expiry_time=expiry_time,
|
||||||
|
enable=True,
|
||||||
|
flow="xtls-rprx-vision",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not response.get("success"):
|
if not response.get("success"):
|
||||||
logger.error(f"Ошибка при обновлении ключа на сервере {server_id} для {client_id}")
|
logger.error(
|
||||||
|
f"Ошибка при обновлении ключа на сервере {server_id} для {client_id}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(f"Ключ успешно обновлен на сервере {server_id} для {client_id}")
|
logger.info(f"Ключ успешно обновлен на сервере {server_id} для {client_id}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при обновлении ключа на сервере {server_id} для {client_id}: {e}")
|
logger.error(
|
||||||
|
f"Ошибка при обновлении ключа на сервере {server_id} для {client_id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('delete_key|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("delete_key|"))
|
||||||
async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
client_id = callback_query.data.split('|')[1]
|
client_id = callback_query.data.split("|")[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
confirmation_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
confirmation_keyboard = types.InlineKeyboardMarkup(
|
||||||
[types.InlineKeyboardButton(text='✅ Да, удалить', callback_data=f'confirm_delete|{client_id}')],
|
inline_keyboard=[
|
||||||
[types.InlineKeyboardButton(text='❌ Нет, отменить', callback_data='view_keys')]
|
[
|
||||||
])
|
types.InlineKeyboardButton(
|
||||||
|
text="✅ Да, удалить",
|
||||||
|
callback_data=f"confirm_delete|{client_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="❌ Нет, отменить", callback_data="view_keys"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id,
|
||||||
text="<b>Вы уверены, что хотите удалить ключ?</b>",
|
text="<b>Вы уверены, что хотите удалить ключ?</b>",
|
||||||
reply_markup=confirmation_keyboard,
|
reply_markup=confirmation_keyboard,
|
||||||
parse_mode="HTML"
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id,
|
||||||
text=f"<b>Ошибка при удалении ключа:</b> {e}",
|
text=f"<b>Ошибка при удалении ключа:</b> {e}",
|
||||||
parse_mode="HTML"
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('renew_key|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("renew_key|"))
|
||||||
async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
async def process_callback_renew_key(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
client_id = callback_query.data.split('|')[1]
|
client_id = callback_query.data.split("|")[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id)
|
record = await conn.fetchrow(
|
||||||
|
"SELECT email, expiry_time FROM keys WHERE client_id = $1", client_id
|
||||||
|
)
|
||||||
|
|
||||||
if record:
|
if record:
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
current_time = datetime.utcnow().timestamp() * 1000
|
current_time = datetime.utcnow().timestamp() * 1000
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = types.InlineKeyboardMarkup(
|
||||||
[types.InlineKeyboardButton(text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)', callback_data=f'renew_plan|1|{client_id}')],
|
inline_keyboard=[
|
||||||
[types.InlineKeyboardButton(text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.)', callback_data=f'renew_plan|3|{client_id}')],
|
[
|
||||||
[types.InlineKeyboardButton(text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.)', callback_data=f'renew_plan|6|{client_id}')],
|
types.InlineKeyboardButton(
|
||||||
[types.InlineKeyboardButton(text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.)', callback_data=f'renew_plan|12|{client_id}')],
|
text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)',
|
||||||
[types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile')]
|
callback_data=f"renew_plan|1|{client_id}",
|
||||||
])
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.)',
|
||||||
|
callback_data=f"renew_plan|3|{client_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.)',
|
||||||
|
callback_data=f"renew_plan|6|{client_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.)',
|
||||||
|
callback_data=f"renew_plan|12|{client_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="🔙 Назад", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
balance = await get_balance(tg_id)
|
balance = await get_balance(tg_id)
|
||||||
response_message = PLAN_SELECTION_MSG.format(balance=balance, expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S'))
|
response_message = PLAN_SELECTION_MSG.format(
|
||||||
|
balance=balance,
|
||||||
|
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
await bot.send_message(chat_id=tg_id, text=response_message, reply_markup=keyboard, parse_mode="HTML")
|
await bot.send_message(
|
||||||
|
chat_id=tg_id,
|
||||||
|
text=response_message,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode="HTML",
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
response_message = "<b>Ключ не найден.</b>"
|
response_message = "<b>Ключ не найден.</b>"
|
||||||
await bot.send_message(chat_id=tg_id, text=response_message, parse_mode="HTML")
|
await bot.send_message(
|
||||||
|
chat_id=tg_id, text=response_message, parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.send_message(chat_id=tg_id, text=f"<b>Ошибка при выборе плана:</b> {e}", parse_mode="HTML")
|
await bot.send_message(
|
||||||
|
chat_id=tg_id,
|
||||||
|
text=f"<b>Ошибка при выборе плана:</b> {e}",
|
||||||
|
parse_mode="HTML",
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('confirm_delete|'))
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("confirm_delete|"))
|
||||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
client_id = callback_query.data.split('|')[1]
|
client_id = callback_query.data.split("|")[1]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('SELECT email FROM keys WHERE client_id = $1', client_id)
|
record = await conn.fetchrow(
|
||||||
|
"SELECT email FROM keys WHERE client_id = $1", client_id
|
||||||
|
)
|
||||||
|
|
||||||
if record:
|
if record:
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
response_message = "Ключ успешно удален."
|
response_message = "Ключ успешно удален."
|
||||||
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="view_keys"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await delete_key(client_id)
|
await delete_key(client_id)
|
||||||
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard)
|
await bot.edit_message_text(
|
||||||
|
response_message,
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
)
|
||||||
|
|
||||||
async def delete_key_from_servers():
|
async def delete_key_from_servers():
|
||||||
try:
|
try:
|
||||||
tasks = []
|
tasks = []
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
tasks.append(delete_key_from_server(server_id, client_id))
|
tasks.append(delete_key_from_server(server_id, client_id))
|
||||||
|
|
||||||
await asyncio.gather(*tasks)
|
await asyncio.gather(*tasks)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -387,16 +563,27 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
response_message = "Ключ не найден или уже удален."
|
response_message = "Ключ не найден или уже удален."
|
||||||
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="view_keys"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard)
|
await bot.edit_message_text(
|
||||||
|
response_message,
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.edit_message_text(f"Ошибка при удалении ключа: {e}", chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.edit_message_text(
|
||||||
|
f"Ошибка при удалении ключа: {e}",
|
||||||
|
chat_id=tg_id,
|
||||||
|
message_id=callback_query.message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@@ -404,74 +591,110 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery):
|
|||||||
async def delete_key_from_server(server_id, client_id):
|
async def delete_key_from_server(server_id, client_id):
|
||||||
"""Удаление ключа с сервера"""
|
"""Удаление ключа с сервера"""
|
||||||
try:
|
try:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
success = await delete_client(session, server_id, client_id)
|
success = await delete_client(session, server_id, client_id)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
logger.error(f"Ошибка удаления ключа {client_id} на сервере {server_id}")
|
logger.error(f"Ошибка удаления ключа {client_id} на сервере {server_id}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении ключа {client_id} с сервера {server_id}: {e}")
|
logger.error(
|
||||||
|
f"Ошибка при удалении ключа {client_id} с сервера {server_id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def delete_key_from_db(client_id):
|
async def delete_key_from_db(client_id):
|
||||||
"""Удаление ключа из базы данных"""
|
"""Удаление ключа из базы данных"""
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id)
|
await conn.execute("DELETE FROM keys WHERE client_id = $1", client_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('renew_plan|'))
|
@router.callback_query(lambda c: c.data.startswith("renew_plan|"))
|
||||||
async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
plan, client_id = callback_query.data.split('|')[1], callback_query.data.split('|')[2]
|
plan, client_id = (
|
||||||
days_to_extend = 30 * int(plan)
|
callback_query.data.split("|")[1],
|
||||||
|
callback_query.data.split("|")[2],
|
||||||
|
)
|
||||||
|
days_to_extend = 30 * int(plan)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id)
|
record = await conn.fetchrow(
|
||||||
|
"SELECT email, expiry_time FROM keys WHERE client_id = $1", client_id
|
||||||
|
)
|
||||||
|
|
||||||
if record:
|
if record:
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
current_time = datetime.utcnow().timestamp() * 1000
|
current_time = datetime.utcnow().timestamp() * 1000
|
||||||
|
|
||||||
if expiry_time <= current_time:
|
if expiry_time <= current_time:
|
||||||
new_expiry_time = int(current_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
new_expiry_time = int(
|
||||||
|
current_time
|
||||||
|
+ timedelta(days=days_to_extend).total_seconds() * 1000
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
|
new_expiry_time = int(
|
||||||
|
expiry_time
|
||||||
|
+ timedelta(days=days_to_extend).total_seconds() * 1000
|
||||||
|
)
|
||||||
|
|
||||||
cost = RENEWAL_PLANS[plan]['price']
|
cost = RENEWAL_PLANS[plan]["price"]
|
||||||
|
|
||||||
balance = await get_balance(tg_id)
|
balance = await get_balance(tg_id)
|
||||||
if balance < cost:
|
if balance < cost:
|
||||||
replenish_button = types.InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')
|
replenish_button = types.InlineKeyboardButton(
|
||||||
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_profile')
|
text="Пополнить баланс", callback_data="replenish_balance"
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [back_button]])
|
)
|
||||||
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
keyboard = types.InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[[replenish_button], [back_button]]
|
||||||
|
)
|
||||||
|
|
||||||
await bot.send_message(tg_id, INSUFFICIENT_FUNDS_MSG, reply_markup=keyboard, parse_mode="HTML")
|
await bot.send_message(
|
||||||
|
tg_id,
|
||||||
|
INSUFFICIENT_FUNDS_MSG,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode="HTML",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
response_message = SUCCESS_RENEWAL_MSG.format(months=RENEWAL_PLANS[plan]['months'])
|
response_message = SUCCESS_RENEWAL_MSG.format(
|
||||||
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_profile')
|
months=RENEWAL_PLANS[plan]["months"]
|
||||||
|
)
|
||||||
|
back_button = types.InlineKeyboardButton(
|
||||||
|
text="Назад", callback_data="view_profile"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||||
|
|
||||||
await bot.send_message(tg_id, response_message, reply_markup=keyboard, parse_mode="HTML")
|
await bot.send_message(
|
||||||
|
tg_id, response_message, reply_markup=keyboard, parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
async def renew_key_on_servers():
|
async def renew_key_on_servers():
|
||||||
tasks = []
|
tasks = []
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
task = asyncio.create_task(
|
task = asyncio.create_task(
|
||||||
renew_server_key(server_id, tg_id, client_id, email, new_expiry_time)
|
renew_server_key(
|
||||||
|
server_id, tg_id, client_id, email, new_expiry_time
|
||||||
|
)
|
||||||
)
|
)
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
|
|
||||||
@@ -489,27 +712,37 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery):
|
|||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}", parse_mode="HTML")
|
await bot.send_message(
|
||||||
|
tg_id, f"Ошибка при продлении ключа: {e}", parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
async def renew_server_key(server_id, tg_id, client_id, email, new_expiry_time):
|
async def renew_server_key(server_id, tg_id, client_id, email, new_expiry_time):
|
||||||
try:
|
try:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
await extend_client_key(session, server_id, tg_id, client_id, email, new_expiry_time)
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
|
await extend_client_key(
|
||||||
|
session, server_id, tg_id, client_id, email, new_expiry_time
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Не удалось продлить ключ {client_id} на сервере {server_id}: {e}")
|
logger.error(
|
||||||
|
f"Не удалось продлить ключ {client_id} на сервере {server_id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def handle_error(tg_id, callback_query, message):
|
async def handle_error(tg_id, callback_query, message):
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await bot.send_message(tg_id, message, parse_mode="HTML")
|
await bot.send_message(tg_id, message, parse_mode="HTML")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при обработке ошибки: {e}")
|
logger.error(f"Ошибка при обработке ошибки: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ from aiohttp import web
|
|||||||
|
|
||||||
from config import SERVERS
|
from config import SERVERS
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -18,54 +20,60 @@ async def fetch_url_content(url):
|
|||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
content = await response.text()
|
content = await response.text()
|
||||||
logger.debug(f"Успешно получен контент с {url}")
|
logger.debug(f"Успешно получен контент с {url}")
|
||||||
return base64.b64decode(content).decode('utf-8').split("\n")
|
return base64.b64decode(content).decode("utf-8").split("\n")
|
||||||
else:
|
else:
|
||||||
logger.error(f"Не удалось получить {url}, статус: {response.status}")
|
logger.error(
|
||||||
return []
|
f"Не удалось получить {url}, статус: {response.status}"
|
||||||
|
)
|
||||||
|
return []
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при получении {url}: {e}")
|
logger.error(f"Ошибка при получении {url}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def combine_unique_lines(urls, query_string):
|
async def combine_unique_lines(urls, query_string):
|
||||||
all_lines = []
|
all_lines = []
|
||||||
logger.debug(f"Начинаем объединение подписок для запроса: {query_string}")
|
logger.debug(f"Начинаем объединение подписок для запроса: {query_string}")
|
||||||
|
|
||||||
urls_with_query = [f"{url}?{query_string}" for url in urls]
|
urls_with_query = [f"{url}?{query_string}" for url in urls]
|
||||||
logger.debug(f"Составлены URL-адреса: {urls_with_query}")
|
logger.debug(f"Составлены URL-адреса: {urls_with_query}")
|
||||||
|
|
||||||
for url in urls_with_query:
|
for url in urls_with_query:
|
||||||
lines = await fetch_url_content(url)
|
lines = await fetch_url_content(url)
|
||||||
all_lines.extend(lines)
|
all_lines.extend(lines)
|
||||||
|
|
||||||
|
|
||||||
all_lines = list(set(filter(None, all_lines)))
|
all_lines = list(set(filter(None, all_lines)))
|
||||||
logger.debug(f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов")
|
logger.debug(
|
||||||
|
f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов"
|
||||||
|
)
|
||||||
|
|
||||||
return all_lines
|
return all_lines
|
||||||
|
|
||||||
|
|
||||||
async def handle_subscription(request):
|
async def handle_subscription(request):
|
||||||
email = request.match_info['email']
|
email = request.match_info["email"]
|
||||||
logger.info(f"Получен запрос на подписку для email: {email}")
|
logger.info(f"Получен запрос на подписку для email: {email}")
|
||||||
|
|
||||||
urls = []
|
urls = []
|
||||||
for server in SERVERS.values():
|
for server in SERVERS.values():
|
||||||
server_subscription_url = f"{server['SUBSCRIPTION']}/{email}"
|
server_subscription_url = f"{server['SUBSCRIPTION']}/{email}"
|
||||||
urls.append(server_subscription_url)
|
urls.append(server_subscription_url)
|
||||||
|
|
||||||
query_string = request.query_string
|
query_string = request.query_string
|
||||||
logger.debug(f"Извлечен query string: {query_string}")
|
logger.debug(f"Извлечен query string: {query_string}")
|
||||||
|
|
||||||
combined_subscriptions = await combine_unique_lines(urls, query_string)
|
combined_subscriptions = await combine_unique_lines(urls, query_string)
|
||||||
|
|
||||||
base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode('utf-8')).decode('utf-8')
|
base64_encoded = base64.b64encode(
|
||||||
|
"\n".join(combined_subscriptions).encode("utf-8")
|
||||||
|
).decode("utf-8")
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'text/plain; charset=utf-8',
|
"Content-Type": "text/plain; charset=utf-8",
|
||||||
'Content-Disposition': 'inline',
|
"Content-Disposition": "inline",
|
||||||
'profile-update-interval': '7',
|
"profile-update-interval": "7",
|
||||||
'profile-title': email,
|
"profile-title": email,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
||||||
return web.Response(text=base64_encoded, headers=headers)
|
return web.Response(text=base64_encoded, headers=headers)
|
||||||
|
|||||||
+46
-22
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
@@ -6,12 +7,13 @@ import asyncpg
|
|||||||
|
|
||||||
from auth import login_with_credentials
|
from auth import login_with_credentials
|
||||||
from client import add_client
|
from client import add_client
|
||||||
from config import (ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, PUBLIC_LINK,
|
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, PUBLIC_LINK, SERVERS
|
||||||
SERVERS)
|
|
||||||
from database import store_key
|
from database import store_key
|
||||||
from handlers.texts import INSTRUCTIONS
|
from handlers.texts import INSTRUCTIONS
|
||||||
from handlers.utils import generate_random_email
|
from handlers.utils import generate_random_email
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
async def create_trial_key(tg_id: int):
|
async def create_trial_key(tg_id: int):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
@@ -21,20 +23,22 @@ async def create_trial_key(tg_id: int):
|
|||||||
|
|
||||||
public_link = f"{PUBLIC_LINK}{email}"
|
public_link = f"{PUBLIC_LINK}{email}"
|
||||||
instructions = INSTRUCTIONS
|
instructions = INSTRUCTIONS
|
||||||
|
|
||||||
result = {
|
result = {"key": public_link, "instructions": instructions}
|
||||||
'key': public_link,
|
|
||||||
'instructions': instructions
|
asyncio.create_task(
|
||||||
}
|
generate_and_store_keys(tg_id, client_id, email, public_link)
|
||||||
|
)
|
||||||
asyncio.create_task(generate_and_store_keys(tg_id, client_id, email, public_link))
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
async def generate_and_store_keys(tg_id: int, client_id: str, email: str, public_link: str):
|
|
||||||
|
async def generate_and_store_keys(
|
||||||
|
tg_id: int, client_id: str, email: str, public_link: str
|
||||||
|
):
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
current_time = datetime.utcnow()
|
current_time = datetime.utcnow()
|
||||||
@@ -43,34 +47,54 @@ async def generate_and_store_keys(tg_id: int, client_id: str, email: str, public
|
|||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
task = create_key_on_server(server_id, client_id, email, tg_id, expiry_timestamp)
|
task = create_key_on_server(
|
||||||
|
server_id, client_id, email, tg_id, expiry_timestamp
|
||||||
|
)
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
|
|
||||||
results = await asyncio.gather(*tasks)
|
results = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
if all(result.get("success") for result in results):
|
if all(result.get("success") for result in results):
|
||||||
await store_key(tg_id, client_id, email, expiry_timestamp, public_link, server_id="all_servers")
|
await store_key(
|
||||||
|
tg_id,
|
||||||
|
client_id,
|
||||||
|
email,
|
||||||
|
expiry_timestamp,
|
||||||
|
public_link,
|
||||||
|
server_id="all_servers",
|
||||||
|
)
|
||||||
|
|
||||||
|
await conn.execute(
|
||||||
await conn.execute('''
|
"""
|
||||||
INSERT INTO connections (tg_id, trial)
|
INSERT INTO connections (tg_id, trial)
|
||||||
VALUES ($1, 1)
|
VALUES ($1, 1)
|
||||||
ON CONFLICT (tg_id)
|
ON CONFLICT (tg_id)
|
||||||
DO UPDATE SET trial = 1
|
DO UPDATE SET trial = 1
|
||||||
''', tg_id)
|
""",
|
||||||
|
tg_id,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print('Не удалось создать ключ на одном или нескольких серверах.')
|
logging.error("Не удалось создать ключ на одном или нескольких серверах.")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
|
|
||||||
|
|
||||||
async def create_key_on_server(server_id: str, client_id: str, email: str, tg_id: int, expiry_timestamp: int):
|
async def create_key_on_server(
|
||||||
|
server_id: str, client_id: str, email: str, tg_id: int, expiry_timestamp: int
|
||||||
|
):
|
||||||
"""Асинхронно создает ключ на указанном сервере и возвращает результат."""
|
"""Асинхронно создает ключ на указанном сервере и возвращает результат."""
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
||||||
response = await add_client(
|
response = await add_client(
|
||||||
session, server_id, client_id, email, tg_id,
|
session,
|
||||||
limit_ip=1, total_gb=0, expiry_time=expiry_timestamp,
|
server_id,
|
||||||
enable=True, flow="xtls-rprx-vision"
|
client_id,
|
||||||
|
email,
|
||||||
|
tg_id,
|
||||||
|
limit_ip=1,
|
||||||
|
total_gb=0,
|
||||||
|
expiry_time=expiry_timestamp,
|
||||||
|
enable=True,
|
||||||
|
flow="xtls-rprx-vision",
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|||||||
+197
-79
@@ -10,33 +10,41 @@ from auth import login_with_credentials
|
|||||||
from client import delete_client, extend_client_key
|
from client import delete_client, extend_client_key
|
||||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
|
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
|
||||||
from database import delete_key, get_balance, update_balance, update_key_expiry
|
from database import delete_key, get_balance, update_balance, update_key_expiry
|
||||||
from handlers.texts import (KEY_EXPIRY_10H, KEY_EXPIRY_24H, KEY_RENEWAL_FAILED,
|
from handlers.texts import (
|
||||||
KEY_RENEWED)
|
KEY_EXPIRY_10H,
|
||||||
|
KEY_EXPIRY_24H,
|
||||||
|
KEY_RENEWAL_FAILED,
|
||||||
|
KEY_RENEWED,
|
||||||
|
)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
class NotificationStates(StatesGroup):
|
class NotificationStates(StatesGroup):
|
||||||
waiting_for_notification_text = State()
|
waiting_for_notification_text = State()
|
||||||
|
|
||||||
|
|
||||||
async def notify_expiring_keys(bot: Bot):
|
async def notify_expiring_keys(bot: Bot):
|
||||||
conn = None
|
conn = None
|
||||||
try:
|
try:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
logger.info("Подключение к базе данных успешно.")
|
logger.info("Подключение к базе данных успешно.")
|
||||||
|
|
||||||
current_time = datetime.utcnow().timestamp() * 1000
|
current_time = datetime.utcnow().timestamp() * 1000
|
||||||
threshold_time_10h = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000
|
threshold_time_10h = (
|
||||||
threshold_time_24h = (datetime.utcnow() + timedelta(days=1)).timestamp() * 1000
|
datetime.utcnow() + timedelta(hours=10)
|
||||||
|
).timestamp() * 1000
|
||||||
|
threshold_time_24h = (datetime.utcnow() + timedelta(days=1)).timestamp() * 1000
|
||||||
|
|
||||||
logger.info("Начало обработки уведомлений.")
|
logger.info("Начало обработки уведомлений.")
|
||||||
|
|
||||||
await notify_10h_keys(bot, conn, current_time, threshold_time_10h)
|
await notify_10h_keys(bot, conn, current_time, threshold_time_10h)
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
await notify_24h_keys(bot, conn, current_time, threshold_time_24h)
|
await notify_24h_keys(bot, conn, current_time, threshold_time_24h)
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
await handle_expired_keys(bot, conn, current_time)
|
await handle_expired_keys(bot, conn, current_time)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -46,25 +54,33 @@ async def notify_expiring_keys(bot: Bot):
|
|||||||
await conn.close()
|
await conn.close()
|
||||||
logger.info("Соединение с базой данных закрыто.")
|
logger.info("Соединение с базой данных закрыто.")
|
||||||
|
|
||||||
|
|
||||||
async def is_bot_blocked(bot: Bot, chat_id: int) -> bool:
|
async def is_bot_blocked(bot: Bot, chat_id: int) -> bool:
|
||||||
try:
|
try:
|
||||||
member = await bot.get_chat_member(chat_id, bot.id)
|
member = await bot.get_chat_member(chat_id, bot.id)
|
||||||
return member.status == 'left'
|
return member.status == "left"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при проверке статуса бота у пользователя {chat_id}: {e}")
|
logger.error(f"Ошибка при проверке статуса бота у пользователя {chat_id}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def notify_10h_keys(bot: Bot, conn: asyncpg.Connection, current_time: float, threshold_time_10h: float):
|
|
||||||
records = await conn.fetch('''
|
async def notify_10h_keys(
|
||||||
|
bot: Bot, conn: asyncpg.Connection, current_time: float, threshold_time_10h: float
|
||||||
|
):
|
||||||
|
records = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
|
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
|
||||||
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified = FALSE
|
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified = FALSE
|
||||||
''', threshold_time_10h, current_time)
|
""",
|
||||||
|
threshold_time_10h,
|
||||||
|
current_time,
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"Найдено {len(records)} ключей для уведомления за 10 часов.")
|
logger.info(f"Найдено {len(records)} ключей для уведомления за 10 часов.")
|
||||||
for record in records:
|
for record in records:
|
||||||
tg_id = record['tg_id']
|
tg_id = record["tg_id"]
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
|
|
||||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||||
current_date = datetime.utcnow()
|
current_date = datetime.utcnow()
|
||||||
@@ -79,42 +95,70 @@ async def notify_10h_keys(bot: Bot, conn: asyncpg.Connection, current_time: floa
|
|||||||
days_left_message = f"{hours_left}"
|
days_left_message = f"{hours_left}"
|
||||||
|
|
||||||
message = KEY_EXPIRY_10H.format(
|
message = KEY_EXPIRY_10H.format(
|
||||||
email=email,
|
email=email,
|
||||||
expiry_date=expiry_date.strftime('%Y-%m-%d %H:%M:%S'),
|
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
days_left_message=days_left_message
|
days_left_message=days_left_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not await is_bot_blocked(bot, tg_id):
|
if not await is_bot_blocked(bot, tg_id):
|
||||||
try:
|
try:
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = types.InlineKeyboardMarkup(
|
||||||
[types.InlineKeyboardButton(text='🔄 Продлить VPN', callback_data=f'renew_key|{record["client_id"]}')],
|
inline_keyboard=[
|
||||||
[types.InlineKeyboardButton(text='💳 Пополнить баланс', callback_data='replenish_balance')],
|
[
|
||||||
[types.InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')]
|
types.InlineKeyboardButton(
|
||||||
])
|
text="🔄 Продлить VPN",
|
||||||
|
callback_data=f'renew_key|{record["client_id"]}',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="💳 Пополнить баланс",
|
||||||
|
callback_data="replenish_balance",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="👤 Мой профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
||||||
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
|
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
|
logger.error(
|
||||||
continue
|
f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
await conn.execute('UPDATE keys SET notified = TRUE WHERE client_id = $1', record['client_id'])
|
await conn.execute(
|
||||||
|
"UPDATE keys SET notified = TRUE WHERE client_id = $1",
|
||||||
|
record["client_id"],
|
||||||
|
)
|
||||||
logger.info(f"Обновлено поле notified для клиента {record['client_id']}.")
|
logger.info(f"Обновлено поле notified для клиента {record['client_id']}.")
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: float, threshold_time_24h: float):
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
async def notify_24h_keys(
|
||||||
|
bot: Bot, conn: asyncpg.Connection, current_time: float, threshold_time_24h: float
|
||||||
|
):
|
||||||
logger.info("Проверка истекших ключей...")
|
logger.info("Проверка истекших ключей...")
|
||||||
|
|
||||||
records_24h = await conn.fetch('''
|
records_24h = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
|
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
|
||||||
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified_24h = FALSE
|
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified_24h = FALSE
|
||||||
''', threshold_time_24h, current_time)
|
""",
|
||||||
|
threshold_time_24h,
|
||||||
|
current_time,
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"Найдено {len(records_24h)} ключей для уведомления за 24 часа.")
|
logger.info(f"Найдено {len(records_24h)} ключей для уведомления за 24 часа.")
|
||||||
for record in records_24h:
|
for record in records_24h:
|
||||||
tg_id = record['tg_id']
|
tg_id = record["tg_id"]
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
|
|
||||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||||
current_date = datetime.utcnow()
|
current_date = datetime.utcnow()
|
||||||
@@ -131,26 +175,49 @@ async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: floa
|
|||||||
message_24h = KEY_EXPIRY_24H.format(
|
message_24h = KEY_EXPIRY_24H.format(
|
||||||
email=email,
|
email=email,
|
||||||
days_left_message=days_left_message,
|
days_left_message=days_left_message,
|
||||||
expiry_date=expiry_date.strftime('%Y-%m-%d %H:%M:%S')
|
expiry_date=expiry_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not await is_bot_blocked(bot, tg_id):
|
if not await is_bot_blocked(bot, tg_id):
|
||||||
try:
|
try:
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = types.InlineKeyboardMarkup(
|
||||||
[types.InlineKeyboardButton(text='🔄 Продлить VPN', callback_data=f'renew_key|{record["client_id"]}')],
|
inline_keyboard=[
|
||||||
[types.InlineKeyboardButton(text='💳 Пополнить баланс', callback_data='replenish_balance')],
|
[
|
||||||
[types.InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')]
|
types.InlineKeyboardButton(
|
||||||
])
|
text="🔄 Продлить VPN",
|
||||||
|
callback_data=f'renew_key|{record["client_id"]}',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="💳 Пополнить баланс",
|
||||||
|
callback_data="replenish_balance",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="👤 Мой профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
|
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
|
||||||
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
|
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}")
|
logger.error(
|
||||||
continue
|
f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
await conn.execute('UPDATE keys SET notified_24h = TRUE WHERE client_id = $1', record['client_id'])
|
await conn.execute(
|
||||||
logger.info(f"Обновлено поле notified_24h для клиента {record['client_id']}.")
|
"UPDATE keys SET notified_24h = TRUE WHERE client_id = $1",
|
||||||
|
record["client_id"],
|
||||||
await asyncio.sleep(1)
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Обновлено поле notified_24h для клиента {record['client_id']}."
|
||||||
|
)
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: float):
|
async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: float):
|
||||||
@@ -159,26 +226,33 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
|||||||
current_time = datetime.utcnow().timestamp() * 1000
|
current_time = datetime.utcnow().timestamp() * 1000
|
||||||
adjusted_current_time = current_time + (3 * 60 * 60 * 1000)
|
adjusted_current_time = current_time + (3 * 60 * 60 * 1000)
|
||||||
|
|
||||||
logger.info(f"Текущее время: {current_time}, Скорректированное текущее время: {adjusted_current_time}")
|
logger.info(
|
||||||
|
f"Текущее время: {current_time}, Скорректированное текущее время: {adjusted_current_time}"
|
||||||
|
)
|
||||||
|
|
||||||
expiring_keys = await conn.fetch('''
|
expiring_keys = await conn.fetch(
|
||||||
|
"""
|
||||||
SELECT tg_id, client_id, expiry_time, email FROM keys
|
SELECT tg_id, client_id, expiry_time, email FROM keys
|
||||||
WHERE expiry_time <= $1
|
WHERE expiry_time <= $1
|
||||||
''', adjusted_current_time)
|
""",
|
||||||
|
adjusted_current_time,
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"Найдено {len(expiring_keys)} истекающих ключей.")
|
logger.info(f"Найдено {len(expiring_keys)} истекающих ключей.")
|
||||||
|
|
||||||
for record in expiring_keys:
|
for record in expiring_keys:
|
||||||
tg_id = record['tg_id']
|
tg_id = record["tg_id"]
|
||||||
client_id = record['client_id']
|
client_id = record["client_id"]
|
||||||
email = record['email']
|
email = record["email"]
|
||||||
balance = await get_balance(tg_id)
|
balance = await get_balance(tg_id)
|
||||||
|
|
||||||
expiry_time = record['expiry_time']
|
expiry_time = record["expiry_time"]
|
||||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||||
current_date = datetime.utcnow()
|
current_date = datetime.utcnow()
|
||||||
time_left = expiry_date - current_date
|
time_left = expiry_date - current_date
|
||||||
logger.info(f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}.")
|
logger.info(
|
||||||
|
f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}."
|
||||||
|
)
|
||||||
|
|
||||||
if time_left.total_seconds() <= 0:
|
if time_left.total_seconds() <= 0:
|
||||||
days_left_message = "Ключ истек"
|
days_left_message = "Ключ истек"
|
||||||
@@ -189,70 +263,114 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
|||||||
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
|
||||||
|
|
||||||
message_expired = f"Ваш ключ {email} истек и был удален!\n\n Перейдите в профиль для создания нового ключа"
|
message_expired = f"Ваш ключ {email} истек и был удален!\n\n Перейдите в профиль для создания нового ключа"
|
||||||
button_profile = types.InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')
|
button_profile = types.InlineKeyboardButton(
|
||||||
|
text="👤 Мой профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[button_profile]])
|
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[button_profile]])
|
||||||
|
|
||||||
if balance >= 100:
|
if balance >= 100:
|
||||||
await update_balance(tg_id, -100)
|
await update_balance(tg_id, -100)
|
||||||
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
|
new_expiry_time = int(
|
||||||
|
(datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
|
||||||
|
)
|
||||||
await update_key_expiry(client_id, new_expiry_time)
|
await update_key_expiry(client_id, new_expiry_time)
|
||||||
logger.info(f"Ключ для клиента {tg_id} продлен до {datetime.utcfromtimestamp(new_expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}.")
|
logger.info(
|
||||||
|
f"Ключ для клиента {tg_id} продлен до {datetime.utcfromtimestamp(new_expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}."
|
||||||
|
)
|
||||||
|
|
||||||
all_success = True
|
all_success = True
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
success = await extend_client_key(session, server_id, tg_id, client_id, email, new_expiry_time)
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
|
success = await extend_client_key(
|
||||||
|
session, server_id, tg_id, client_id, email, new_expiry_time
|
||||||
|
)
|
||||||
if not success:
|
if not success:
|
||||||
all_success = False
|
all_success = False
|
||||||
logger.error(f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}.")
|
logger.error(
|
||||||
|
f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}."
|
||||||
|
)
|
||||||
|
|
||||||
if all_success:
|
if all_success:
|
||||||
try:
|
try:
|
||||||
await bot.send_message(tg_id, KEY_RENEWED, reply_markup=keyboard)
|
await bot.send_message(tg_id, KEY_RENEWED, reply_markup=keyboard)
|
||||||
logger.info(f"Ключ для пользователя {tg_id} успешно продлен на месяц на всех серверах.")
|
logger.info(
|
||||||
|
f"Ключ для пользователя {tg_id} успешно продлен на месяц на всех серверах."
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 'blocked' in str(e).lower():
|
if "blocked" in str(e).lower():
|
||||||
logger.warning(f"Пользователь {tg_id} заблокирован. Ключ будет удален.")
|
logger.warning(
|
||||||
|
f"Пользователь {tg_id} заблокирован. Ключ будет удален."
|
||||||
|
)
|
||||||
await delete_key(client_id)
|
await delete_key(client_id)
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
success = await delete_client(session, server_id, client_id)
|
success = await delete_client(session, server_id, client_id)
|
||||||
if success:
|
if success:
|
||||||
logger.info(f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}.")
|
logger.info(
|
||||||
|
f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}.")
|
logger.error(
|
||||||
|
f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Ошибка при отправке уведомления о продлении ключа пользователю {tg_id}: {e}")
|
logger.error(
|
||||||
|
f"Ошибка при отправке уведомления о продлении ключа пользователю {tg_id}: {e}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
await bot.send_message(tg_id, KEY_RENEWAL_FAILED, reply_markup=keyboard)
|
await bot.send_message(
|
||||||
logger.error(f"Не удалось продлить ключ для пользователя {tg_id} на одном или нескольких серверах.")
|
tg_id, KEY_RENEWAL_FAILED, reply_markup=keyboard
|
||||||
|
)
|
||||||
|
logger.error(
|
||||||
|
f"Не удалось продлить ключ для пользователя {tg_id} на одном или нескольких серверах."
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при отправке уведомления о неудачном продлении ключа пользователю {tg_id}: {e}")
|
logger.error(
|
||||||
|
f"Ошибка при отправке уведомления о неудачном продлении ключа пользователю {tg_id}: {e}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
await bot.send_message(tg_id, message_expired, reply_markup=keyboard)
|
await bot.send_message(tg_id, message_expired, reply_markup=keyboard)
|
||||||
await delete_key(client_id)
|
await delete_key(client_id)
|
||||||
|
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
success = await delete_client(session, server_id, client_id)
|
success = await delete_client(session, server_id, client_id)
|
||||||
if success:
|
if success:
|
||||||
logger.info(f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}.")
|
logger.info(
|
||||||
|
f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}.")
|
logger.error(
|
||||||
|
f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}."
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 'blocked' in str(e).lower():
|
if "blocked" in str(e).lower():
|
||||||
logger.warning(f"Пользователь {tg_id} заблокирован. Ключ будет удален.")
|
logger.warning(
|
||||||
|
f"Пользователь {tg_id} заблокирован. Ключ будет удален."
|
||||||
|
)
|
||||||
await delete_key(client_id)
|
await delete_key(client_id)
|
||||||
for server_id in SERVERS:
|
for server_id in SERVERS:
|
||||||
session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD)
|
session = await login_with_credentials(
|
||||||
|
server_id, ADMIN_USERNAME, ADMIN_PASSWORD
|
||||||
|
)
|
||||||
success = await delete_client(session, server_id, client_id)
|
success = await delete_client(session, server_id, client_id)
|
||||||
if success:
|
if success:
|
||||||
logger.info(f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}.")
|
logger.info(
|
||||||
|
f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}.")
|
logger.error(
|
||||||
|
f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Ошибка при удалении ключа для клиента {tg_id}: {e}")
|
logger.error(f"Ошибка при удалении ключа для клиента {tg_id}: {e}")
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|||||||
@@ -17,15 +17,18 @@ from database import update_balance
|
|||||||
router = Router()
|
router = Router()
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
class ReplenishBalanceState(StatesGroup):
|
class ReplenishBalanceState(StatesGroup):
|
||||||
choosing_amount = State()
|
choosing_amount = State()
|
||||||
waiting_for_payment_confirmation = State()
|
waiting_for_payment_confirmation = State()
|
||||||
entering_custom_amount = State()
|
entering_custom_amount = State()
|
||||||
|
|
||||||
|
|
||||||
def generate_signature(params, api_key):
|
def generate_signature(params, api_key):
|
||||||
sign_string = ":".join([str(params[k]) for k in sorted(params)]) + api_key
|
sign_string = ":".join([str(params[k]) for k in sorted(params)]) + api_key
|
||||||
return hashlib.md5(sign_string.encode()).hexdigest()
|
return hashlib.md5(sign_string.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
async def create_payment(user_id, amount, email, ip):
|
async def create_payment(user_id, amount, email, ip):
|
||||||
payment_id = str(uuid.uuid4())
|
payment_id = str(uuid.uuid4())
|
||||||
nonce = int(time.time() * 1000)
|
nonce = int(time.time() * 1000)
|
||||||
@@ -36,19 +39,21 @@ async def create_payment(user_id, amount, email, ip):
|
|||||||
"paymentId": payment_id,
|
"paymentId": payment_id,
|
||||||
"email": email,
|
"email": email,
|
||||||
"ip": ip,
|
"ip": ip,
|
||||||
"i": 6,
|
"i": 6,
|
||||||
"nonce": nonce
|
"nonce": nonce,
|
||||||
}
|
}
|
||||||
params["signature"] = generate_signature(params, FREEKASSA_API_KEY)
|
params["signature"] = generate_signature(params, FREEKASSA_API_KEY)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.post("https://api.freekassa.com/v1/orders/create", json=params)
|
response = requests.post(
|
||||||
|
"https://api.freekassa.com/v1/orders/create", json=params
|
||||||
|
)
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
|
|
||||||
logging.debug(f"Ответ от FreeKassa при создании платежа: {response_data}")
|
logging.debug(f"Ответ от FreeKassa при создании платежа: {response_data}")
|
||||||
|
|
||||||
if response_data.get("type") == "success":
|
if response_data.get("type") == "success":
|
||||||
return response_data["location"]
|
return response_data["location"]
|
||||||
else:
|
else:
|
||||||
logging.error(f"Ошибка создания платежа: {response_data}")
|
logging.error(f"Ошибка создания платежа: {response_data}")
|
||||||
return None
|
return None
|
||||||
@@ -57,15 +62,17 @@ async def create_payment(user_id, amount, email, ip):
|
|||||||
logging.error(f"Ошибка запроса к FreeKassa: {e}")
|
logging.error(f"Ошибка запроса к FreeKassa: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def send_payment_success_notification(user_id, amount):
|
async def send_payment_success_notification(user_id, amount):
|
||||||
try:
|
try:
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=user_id,
|
chat_id=user_id,
|
||||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!"
|
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
logging.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def freekassa_webhook(request):
|
async def freekassa_webhook(request):
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
logging.debug(f"Получен вебхук от FreeKassa: {data}")
|
logging.debug(f"Получен вебхук от FreeKassa: {data}")
|
||||||
@@ -81,38 +88,50 @@ async def freekassa_webhook(request):
|
|||||||
|
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'pay_freekassa')
|
|
||||||
async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, state: FSMContext):
|
@router.callback_query(lambda c: c.data == "pay_freekassa")
|
||||||
|
async def process_callback_pay_freekassa(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
|
|
||||||
amount_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
amount_keyboard = InlineKeyboardMarkup(
|
||||||
[
|
inline_keyboard=[
|
||||||
InlineKeyboardButton(text="100 рублей", callback_data="amount|100"),
|
[
|
||||||
InlineKeyboardButton(text="500 рублей", callback_data="amount|500")
|
InlineKeyboardButton(text="100 рублей", callback_data="amount|100"),
|
||||||
],
|
InlineKeyboardButton(text="500 рублей", callback_data="amount|500"),
|
||||||
[
|
],
|
||||||
InlineKeyboardButton(text="1000 рублей", callback_data="amount|1000"),
|
[
|
||||||
InlineKeyboardButton(text="5000 рублей", callback_data="amount|5000")
|
InlineKeyboardButton(text="1000 рублей", callback_data="amount|1000"),
|
||||||
],
|
InlineKeyboardButton(text="5000 рублей", callback_data="amount|5000"),
|
||||||
[
|
],
|
||||||
InlineKeyboardButton(text="Введите другую сумму", callback_data="enter_custom_amount")
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Введите другую сумму", callback_data="enter_custom_amount"
|
||||||
|
)
|
||||||
|
],
|
||||||
]
|
]
|
||||||
])
|
)
|
||||||
|
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id,
|
||||||
text="Выберите сумму пополнения через FreeKassa:",
|
text="Выберите сумму пополнения через FreeKassa:",
|
||||||
reply_markup=amount_keyboard
|
reply_markup=amount_keyboard,
|
||||||
)
|
)
|
||||||
|
|
||||||
await state.set_state(ReplenishBalanceState.choosing_amount)
|
await state.set_state(ReplenishBalanceState.choosing_amount)
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('amount|'))
|
|
||||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
@router.callback_query(lambda c: c.data.startswith("amount|"))
|
||||||
data = callback_query.data.split('|', 1)
|
async def process_amount_selection(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
data = callback_query.data.split("|", 1)
|
||||||
amount_str = data[1]
|
amount_str = data[1]
|
||||||
try:
|
try:
|
||||||
amount = int(amount_str)
|
amount = int(amount_str)
|
||||||
@@ -120,39 +139,55 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
|
|||||||
await bot.send_message(callback_query.from_user.id, "Некорректная сумма.")
|
await bot.send_message(callback_query.from_user.id, "Некорректная сумма.")
|
||||||
return
|
return
|
||||||
|
|
||||||
user_email = f"{callback_query.from_user.id}@solo.net"
|
user_email = f"{callback_query.from_user.id}@solo.net"
|
||||||
user_ip = callback_query.message.chat.id
|
user_ip = callback_query.message.chat.id
|
||||||
payment_url = await create_payment(callback_query.from_user.id, amount, user_email, user_ip)
|
payment_url = await create_payment(
|
||||||
|
callback_query.from_user.id, amount, user_email, user_ip
|
||||||
|
)
|
||||||
|
|
||||||
if payment_url:
|
if payment_url:
|
||||||
await bot.send_message(callback_query.from_user.id, f"Перейдите по ссылке для оплаты: {payment_url}")
|
await bot.send_message(
|
||||||
|
callback_query.from_user.id,
|
||||||
|
f"Перейдите по ссылке для оплаты: {payment_url}",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await bot.send_message(callback_query.from_user.id, "Ошибка при создании платежа. Попробуйте позже.")
|
await bot.send_message(
|
||||||
|
callback_query.from_user.id,
|
||||||
|
"Ошибка при создании платежа. Попробуйте позже.",
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'enter_custom_amount')
|
|
||||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
@router.callback_query(lambda c: c.data == "enter_custom_amount")
|
||||||
await callback_query.message.edit_text(
|
async def process_enter_custom_amount(
|
||||||
text="Введите сумму пополнения:"
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
)
|
):
|
||||||
|
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||||
await state.set_state(ReplenishBalanceState.entering_custom_amount)
|
await state.set_state(ReplenishBalanceState.entering_custom_amount)
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
@router.message(ReplenishBalanceState.entering_custom_amount)
|
@router.message(ReplenishBalanceState.entering_custom_amount)
|
||||||
async def process_custom_amount_input(message: types.Message, state: FSMContext):
|
async def process_custom_amount_input(message: types.Message, state: FSMContext):
|
||||||
if message.text.isdigit():
|
if message.text.isdigit():
|
||||||
amount = int(message.text)
|
amount = int(message.text)
|
||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
|
await message.answer(
|
||||||
|
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
user_email = f"{message.from_user.id}@solo.net"
|
user_email = f"{message.from_user.id}@solo.net"
|
||||||
user_ip = message.chat.id
|
user_ip = message.chat.id
|
||||||
payment_url = await create_payment(message.from_user.id, amount, user_email, user_ip)
|
payment_url = await create_payment(
|
||||||
|
message.from_user.id, amount, user_email, user_ip
|
||||||
|
)
|
||||||
|
|
||||||
if payment_url:
|
if payment_url:
|
||||||
await bot.send_message(message.from_user.id, f"Перейдите по ссылке для оплаты: {payment_url}")
|
await bot.send_message(
|
||||||
|
message.from_user.id, f"Перейдите по ссылке для оплаты: {payment_url}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await message.answer("Ошибка при создании платежа. Попробуйте позже.")
|
await message.answer("Ошибка при создании платежа. Попробуйте позже.")
|
||||||
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
import logging
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from aiogram import Router, types
|
|
||||||
from aiogram.fsm.context import FSMContext
|
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
|
||||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
|
||||||
from aiohttp import web
|
|
||||||
from yookassa import Configuration, Payment
|
|
||||||
|
|
||||||
from bot import bot
|
|
||||||
from config import YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
|
|
||||||
from database import (add_connection, check_connection_exists, get_key_count,
|
|
||||||
update_balance)
|
|
||||||
from handlers.profile import process_callback_view_profile
|
|
||||||
from handlers.texts import PAYMENT_OPTIONS
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
Configuration.account_id = YOOKASSA_SHOP_ID
|
|
||||||
Configuration.secret_key = YOOKASSA_SECRET_KEY
|
|
||||||
|
|
||||||
logging.debug(f"Account ID: {YOOKASSA_SHOP_ID}")
|
|
||||||
logging.debug(f"Secret Key: {YOOKASSA_SECRET_KEY}")
|
|
||||||
|
|
||||||
class ReplenishBalanceState(StatesGroup):
|
|
||||||
choosing_amount = State()
|
|
||||||
waiting_for_payment_confirmation = State()
|
|
||||||
entering_custom_amount = State()
|
|
||||||
|
|
||||||
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key='last_message_id'):
|
|
||||||
if state:
|
|
||||||
try:
|
|
||||||
state_data = await state.get_data()
|
|
||||||
previous_message_id = state_data.get(message_key)
|
|
||||||
|
|
||||||
if previous_message_id:
|
|
||||||
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
|
|
||||||
|
|
||||||
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
|
|
||||||
await state.update_data({message_key: sent_message.message_id})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка при удалении/отправке сообщения: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
return sent_message
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'replenish_balance')
|
|
||||||
async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext):
|
|
||||||
tg_id = callback_query.from_user.id
|
|
||||||
|
|
||||||
key_count = await get_key_count(tg_id)
|
|
||||||
|
|
||||||
if key_count == 0:
|
|
||||||
exists = await check_connection_exists(tg_id)
|
|
||||||
if not exists:
|
|
||||||
await add_connection(tg_id, balance=0.0, trial=0)
|
|
||||||
|
|
||||||
amount_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text=PAYMENT_OPTIONS[0]['text'], callback_data=PAYMENT_OPTIONS[0]['callback_data']),
|
|
||||||
InlineKeyboardButton(text=PAYMENT_OPTIONS[1]['text'], callback_data=PAYMENT_OPTIONS[1]['callback_data'])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text=PAYMENT_OPTIONS[2]['text'], callback_data=PAYMENT_OPTIONS[2]['callback_data']),
|
|
||||||
InlineKeyboardButton(text=PAYMENT_OPTIONS[3]['text'], callback_data=PAYMENT_OPTIONS[3]['callback_data'])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text=PAYMENT_OPTIONS[4]['text'], callback_data=PAYMENT_OPTIONS[4]['callback_data'])
|
|
||||||
],
|
|
||||||
[
|
|
||||||
InlineKeyboardButton(text=PAYMENT_OPTIONS[5]['text'], callback_data=PAYMENT_OPTIONS[5]['callback_data'])
|
|
||||||
],
|
|
||||||
])
|
|
||||||
|
|
||||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
|
||||||
|
|
||||||
await bot.send_message(
|
|
||||||
chat_id=tg_id,
|
|
||||||
text="Выберите сумму пополнения:",
|
|
||||||
reply_markup=amount_keyboard
|
|
||||||
)
|
|
||||||
|
|
||||||
await state.set_state(ReplenishBalanceState.choosing_amount)
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'back_to_profile')
|
|
||||||
async def back_to_profile_handler(callback_query: types.CallbackQuery, state: FSMContext):
|
|
||||||
await process_callback_view_profile(callback_query, state)
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data.startswith('amount_'))
|
|
||||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
|
||||||
data = callback_query.data.split('_', 1)
|
|
||||||
|
|
||||||
if len(data) != 2:
|
|
||||||
await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для выбора суммы.", state=state, message_key='amount_error_message_id')
|
|
||||||
return
|
|
||||||
|
|
||||||
amount_str = data[1]
|
|
||||||
try:
|
|
||||||
amount = int(amount_str)
|
|
||||||
except ValueError:
|
|
||||||
await send_message_with_deletion(callback_query.from_user.id, "Некорректная сумма.", state=state, message_key='amount_error_message_id')
|
|
||||||
return
|
|
||||||
|
|
||||||
await state.update_data(amount=amount)
|
|
||||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation)
|
|
||||||
|
|
||||||
state_data = await state.get_data()
|
|
||||||
customer_name = callback_query.from_user.full_name
|
|
||||||
customer_id = callback_query.from_user.id
|
|
||||||
|
|
||||||
customer_email = f"{customer_id}@solo.net"
|
|
||||||
|
|
||||||
payment = Payment.create({
|
|
||||||
"amount": {
|
|
||||||
"value": str(amount),
|
|
||||||
"currency": "RUB"
|
|
||||||
},
|
|
||||||
"confirmation": {
|
|
||||||
"type": "redirect",
|
|
||||||
"return_url": "https://pocomacho.ru/"
|
|
||||||
},
|
|
||||||
"capture": True,
|
|
||||||
"description": "Пополнение баланса",
|
|
||||||
"receipt": {
|
|
||||||
"customer": {
|
|
||||||
"full_name": customer_name,
|
|
||||||
"email": customer_email,
|
|
||||||
"phone": "79000000000"
|
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"description": "Пополнение баланса",
|
|
||||||
"quantity": "1.00",
|
|
||||||
"amount": {
|
|
||||||
"value": str(amount),
|
|
||||||
"currency": "RUB"
|
|
||||||
},
|
|
||||||
"vat_code": 6
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"user_id": customer_id
|
|
||||||
}
|
|
||||||
}, uuid.uuid4())
|
|
||||||
|
|
||||||
if payment['status'] == 'pending':
|
|
||||||
payment_url = payment['confirmation']['confirmation_url']
|
|
||||||
|
|
||||||
confirm_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
||||||
[InlineKeyboardButton(text='Пополнить', url=payment_url)],
|
|
||||||
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')]
|
|
||||||
])
|
|
||||||
|
|
||||||
await callback_query.message.edit_text(
|
|
||||||
text=f"Вы выбрали пополнение на {amount} рублей.",
|
|
||||||
reply_markup=confirm_keyboard
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await send_message_with_deletion(callback_query.from_user.id, "Ошибка при создании платежа.", state=state)
|
|
||||||
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
async def send_payment_success_notification(user_id: int, amount: float):
|
|
||||||
try:
|
|
||||||
profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
||||||
[InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')]
|
|
||||||
])
|
|
||||||
|
|
||||||
await bot.send_message(
|
|
||||||
chat_id=user_id,
|
|
||||||
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
|
||||||
reply_markup=profile_keyboard
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
|
||||||
|
|
||||||
async def payment_webhook(request):
|
|
||||||
event = await request.json()
|
|
||||||
|
|
||||||
logging.debug(f"Webhook event received: {event}")
|
|
||||||
|
|
||||||
if event['event'] == 'payment.succeeded':
|
|
||||||
user_id_str = event['object']['metadata']['user_id']
|
|
||||||
amount_str = event['object']['amount']['value']
|
|
||||||
|
|
||||||
try:
|
|
||||||
user_id = int(user_id_str)
|
|
||||||
amount = float(amount_str)
|
|
||||||
|
|
||||||
logging.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
|
|
||||||
await update_balance(user_id, amount)
|
|
||||||
|
|
||||||
await send_payment_success_notification(user_id, amount)
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
logging.error(f"Ошибка конвертации user_id или amount: {e}")
|
|
||||||
return web.Response(status=400)
|
|
||||||
|
|
||||||
return web.Response(status=200)
|
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'enter_custom_amount')
|
|
||||||
async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
|
||||||
await callback_query.message.edit_text(
|
|
||||||
text="Введите сумму пополнения:"
|
|
||||||
)
|
|
||||||
await state.set_state(ReplenishBalanceState.entering_custom_amount)
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@router.message(State(ReplenishBalanceState.entering_custom_amount))
|
|
||||||
async def process_custom_amount_input(message: types.Message, state: FSMContext):
|
|
||||||
if message.text.isdigit():
|
|
||||||
amount = int(message.text)
|
|
||||||
if amount <= 0:
|
|
||||||
await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
|
|
||||||
return
|
|
||||||
|
|
||||||
await state.update_data(amount=amount)
|
|
||||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation)
|
|
||||||
|
|
||||||
try:
|
|
||||||
payment = Payment.create({
|
|
||||||
"amount": {
|
|
||||||
"value": str(amount),
|
|
||||||
"currency": "RUB"
|
|
||||||
},
|
|
||||||
"confirmation": {
|
|
||||||
"type": "redirect",
|
|
||||||
"return_url": "https://pocomacho.ru/"
|
|
||||||
},
|
|
||||||
"capture": True,
|
|
||||||
"description": "Пополнение баланса",
|
|
||||||
"receipt": {
|
|
||||||
"customer": {
|
|
||||||
"full_name": message.from_user.full_name,
|
|
||||||
"email": f"{message.from_user.id}@solo.net",
|
|
||||||
"phone": "79000000000"
|
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"description": "Пополнение баланса",
|
|
||||||
"quantity": "1.00",
|
|
||||||
"amount": {
|
|
||||||
"value": str(amount),
|
|
||||||
"currency": "RUB"
|
|
||||||
},
|
|
||||||
"vat_code": 6
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"user_id": message.from_user.id
|
|
||||||
}
|
|
||||||
}, uuid.uuid4())
|
|
||||||
|
|
||||||
if payment['status'] == 'pending':
|
|
||||||
payment_url = payment['confirmation']['confirmation_url']
|
|
||||||
|
|
||||||
confirm_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
||||||
[InlineKeyboardButton(text='Пополнить', url=payment_url)],
|
|
||||||
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')]
|
|
||||||
])
|
|
||||||
|
|
||||||
await message.answer(
|
|
||||||
text=f"Вы выбрали пополнение на {amount} рублей.",
|
|
||||||
reply_markup=confirm_keyboard
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await message.answer("Ошибка при создании платежа.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Ошибка при создании платежа: {e}")
|
|
||||||
await message.answer("Произошла ошибка при создании платежа.")
|
|
||||||
else:
|
|
||||||
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
|
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from aiogram import Router, types
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
from aiohttp import web
|
||||||
|
from yookassa import Configuration, Payment
|
||||||
|
|
||||||
|
from bot import bot
|
||||||
|
from config import YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
|
||||||
|
from database import (
|
||||||
|
add_connection,
|
||||||
|
check_connection_exists,
|
||||||
|
get_key_count,
|
||||||
|
update_balance,
|
||||||
|
)
|
||||||
|
from handlers.profile import process_callback_view_profile
|
||||||
|
from handlers.texts import PAYMENT_OPTIONS
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
Configuration.account_id = YOOKASSA_SHOP_ID
|
||||||
|
Configuration.secret_key = YOOKASSA_SECRET_KEY
|
||||||
|
|
||||||
|
logging.debug(f"Account ID: {YOOKASSA_SHOP_ID}")
|
||||||
|
logging.debug(f"Secret Key: {YOOKASSA_SECRET_KEY}")
|
||||||
|
|
||||||
|
|
||||||
|
class ReplenishBalanceState(StatesGroup):
|
||||||
|
choosing_amount = State()
|
||||||
|
waiting_for_payment_confirmation = State()
|
||||||
|
entering_custom_amount = State()
|
||||||
|
|
||||||
|
|
||||||
|
async def send_message_with_deletion(
|
||||||
|
chat_id, text, reply_markup=None, state=None, message_key="last_message_id"
|
||||||
|
):
|
||||||
|
if state:
|
||||||
|
try:
|
||||||
|
state_data = await state.get_data()
|
||||||
|
previous_message_id = state_data.get(message_key)
|
||||||
|
|
||||||
|
if previous_message_id:
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=chat_id, message_id=previous_message_id
|
||||||
|
)
|
||||||
|
|
||||||
|
sent_message = await bot.send_message(
|
||||||
|
chat_id=chat_id, text=text, reply_markup=reply_markup
|
||||||
|
)
|
||||||
|
await state.update_data({message_key: sent_message.message_id})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при удалении/отправке сообщения: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return sent_message
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "replenish_balance")
|
||||||
|
async def process_callback_replenish_balance(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
tg_id = callback_query.from_user.id
|
||||||
|
|
||||||
|
key_count = await get_key_count(tg_id)
|
||||||
|
|
||||||
|
if key_count == 0:
|
||||||
|
exists = await check_connection_exists(tg_id)
|
||||||
|
if not exists:
|
||||||
|
await add_connection(tg_id, balance=0.0, trial=0)
|
||||||
|
|
||||||
|
amount_keyboard = InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[0]["text"],
|
||||||
|
callback_data=PAYMENT_OPTIONS[0]["callback_data"],
|
||||||
|
),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[1]["text"],
|
||||||
|
callback_data=PAYMENT_OPTIONS[1]["callback_data"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[2]["text"],
|
||||||
|
callback_data=PAYMENT_OPTIONS[2]["callback_data"],
|
||||||
|
),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[3]["text"],
|
||||||
|
callback_data=PAYMENT_OPTIONS[3]["callback_data"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[4]["text"],
|
||||||
|
callback_data=PAYMENT_OPTIONS[4]["callback_data"],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[5]["text"],
|
||||||
|
callback_data=PAYMENT_OPTIONS[5]["callback_data"],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=tg_id, text="Выберите сумму пополнения:", reply_markup=amount_keyboard
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.set_state(ReplenishBalanceState.choosing_amount)
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "back_to_profile")
|
||||||
|
async def back_to_profile_handler(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
await process_callback_view_profile(callback_query, state)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data.startswith("amount_"))
|
||||||
|
async def process_amount_selection(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
data = callback_query.data.split("_", 1)
|
||||||
|
|
||||||
|
if len(data) != 2:
|
||||||
|
await send_message_with_deletion(
|
||||||
|
callback_query.from_user.id,
|
||||||
|
"Неверные данные для выбора суммы.",
|
||||||
|
state=state,
|
||||||
|
message_key="amount_error_message_id",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
amount_str = data[1]
|
||||||
|
try:
|
||||||
|
amount = int(amount_str)
|
||||||
|
except ValueError:
|
||||||
|
await send_message_with_deletion(
|
||||||
|
callback_query.from_user.id,
|
||||||
|
"Некорректная сумма.",
|
||||||
|
state=state,
|
||||||
|
message_key="amount_error_message_id",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(amount=amount)
|
||||||
|
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation)
|
||||||
|
|
||||||
|
state_data = await state.get_data()
|
||||||
|
customer_name = callback_query.from_user.full_name
|
||||||
|
customer_id = callback_query.from_user.id
|
||||||
|
|
||||||
|
customer_email = f"{customer_id}@solo.net"
|
||||||
|
|
||||||
|
payment = Payment.create(
|
||||||
|
{
|
||||||
|
"amount": {"value": str(amount), "currency": "RUB"},
|
||||||
|
"confirmation": {"type": "redirect", "return_url": "https://pocomacho.ru/"},
|
||||||
|
"capture": True,
|
||||||
|
"description": "Пополнение баланса",
|
||||||
|
"receipt": {
|
||||||
|
"customer": {
|
||||||
|
"full_name": customer_name,
|
||||||
|
"email": customer_email,
|
||||||
|
"phone": "79000000000",
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"description": "Пополнение баланса",
|
||||||
|
"quantity": "1.00",
|
||||||
|
"amount": {"value": str(amount), "currency": "RUB"},
|
||||||
|
"vat_code": 6,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"metadata": {"user_id": customer_id},
|
||||||
|
},
|
||||||
|
uuid.uuid4(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if payment["status"] == "pending":
|
||||||
|
payment_url = payment["confirmation"]["confirmation_url"]
|
||||||
|
|
||||||
|
confirm_keyboard = InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="Пополнить", url=payment_url)],
|
||||||
|
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback_query.message.edit_text(
|
||||||
|
text=f"Вы выбрали пополнение на {amount} рублей.",
|
||||||
|
reply_markup=confirm_keyboard,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await send_message_with_deletion(
|
||||||
|
callback_query.from_user.id, "Ошибка при создании платежа.", state=state
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
async def send_payment_success_notification(user_id: int, amount: float):
|
||||||
|
try:
|
||||||
|
profile_keyboard = InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="Перейти в профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=user_id,
|
||||||
|
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||||
|
reply_markup=profile_keyboard,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при отправке уведомления пользователю {user_id}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
async def yookassa_webhook(request):
|
||||||
|
event = await request.json()
|
||||||
|
|
||||||
|
logging.debug(f"Webhook event received: {event}")
|
||||||
|
|
||||||
|
if event["event"] == "payment.succeeded":
|
||||||
|
user_id_str = event["object"]["metadata"]["user_id"]
|
||||||
|
amount_str = event["object"]["amount"]["value"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_id = int(user_id_str)
|
||||||
|
amount = float(amount_str)
|
||||||
|
|
||||||
|
logging.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}")
|
||||||
|
await update_balance(user_id, amount)
|
||||||
|
|
||||||
|
await send_payment_success_notification(user_id, amount)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logging.error(f"Ошибка конвертации user_id или amount: {e}")
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(lambda c: c.data == "enter_custom_amount")
|
||||||
|
async def process_enter_custom_amount(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
await callback_query.message.edit_text(text="Введите сумму пополнения:")
|
||||||
|
await state.set_state(ReplenishBalanceState.entering_custom_amount)
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(State(ReplenishBalanceState.entering_custom_amount))
|
||||||
|
async def process_custom_amount_input(message: types.Message, state: FSMContext):
|
||||||
|
if message.text.isdigit():
|
||||||
|
amount = int(message.text)
|
||||||
|
if amount <= 0:
|
||||||
|
await message.answer(
|
||||||
|
"Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(amount=amount)
|
||||||
|
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation)
|
||||||
|
|
||||||
|
try:
|
||||||
|
payment = Payment.create(
|
||||||
|
{
|
||||||
|
"amount": {"value": str(amount), "currency": "RUB"},
|
||||||
|
"confirmation": {
|
||||||
|
"type": "redirect",
|
||||||
|
"return_url": "https://pocomacho.ru/",
|
||||||
|
},
|
||||||
|
"capture": True,
|
||||||
|
"description": "Пополнение баланса",
|
||||||
|
"receipt": {
|
||||||
|
"customer": {
|
||||||
|
"full_name": message.from_user.full_name,
|
||||||
|
"email": f"{message.from_user.id}@solo.net",
|
||||||
|
"phone": "79000000000",
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"description": "Пополнение баланса",
|
||||||
|
"quantity": "1.00",
|
||||||
|
"amount": {"value": str(amount), "currency": "RUB"},
|
||||||
|
"vat_code": 6,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"metadata": {"user_id": message.from_user.id},
|
||||||
|
},
|
||||||
|
uuid.uuid4(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if payment["status"] == "pending":
|
||||||
|
payment_url = payment["confirmation"]["confirmation_url"]
|
||||||
|
|
||||||
|
confirm_keyboard = InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="Пополнить", url=payment_url)],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="⬅️ Назад", callback_data="back_to_profile"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
text=f"Вы выбрали пополнение на {amount} рублей.",
|
||||||
|
reply_markup=confirm_keyboard,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await message.answer("Ошибка при создании платежа.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при создании платежа: {e}")
|
||||||
|
await message.answer("Произошла ошибка при создании платежа.")
|
||||||
|
else:
|
||||||
|
await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:")
|
||||||
+68
-39
@@ -4,30 +4,37 @@ import os
|
|||||||
from aiogram import Router, types
|
from aiogram import Router, types
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
from aiogram.types import (BufferedInputFile, InlineKeyboardButton,
|
from aiogram.types import BufferedInputFile, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
InlineKeyboardMarkup)
|
|
||||||
|
|
||||||
from bot import bot
|
from bot import bot
|
||||||
from config import PAYMENT_METHOD
|
from config import PAYMENT_METHOD
|
||||||
from database import get_balance, get_key_count, get_referral_stats
|
from database import get_balance, get_key_count, get_referral_stats
|
||||||
from handlers.texts import (CHANNEL_LINK, get_referral_link,
|
from handlers.texts import (
|
||||||
invite_message_send, profile_message_send)
|
CHANNEL_LINK,
|
||||||
|
get_referral_link,
|
||||||
|
invite_message_send,
|
||||||
|
profile_message_send,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReplenishBalanceState(StatesGroup):
|
class ReplenishBalanceState(StatesGroup):
|
||||||
choosing_transfer_method = State()
|
choosing_transfer_method = State()
|
||||||
waiting_for_admin_confirmation = State()
|
waiting_for_admin_confirmation = State()
|
||||||
|
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext):
|
|
||||||
tg_id = callback_query.from_user.id
|
|
||||||
username = callback_query.from_user.full_name
|
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), 'pic.jpg')
|
async def process_callback_view_profile(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
tg_id = callback_query.from_user.id
|
||||||
|
username = callback_query.from_user.full_name
|
||||||
|
|
||||||
|
image_path = os.path.join(os.path.dirname(__file__), "pic.jpg")
|
||||||
|
|
||||||
if not os.path.isfile(image_path):
|
if not os.path.isfile(image_path):
|
||||||
await bot.send_message(tg_id, "Файл изображения не найден.")
|
await bot.send_message(tg_id, "Файл изображения не найден.")
|
||||||
@@ -37,37 +44,62 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
|
|||||||
key_count = await get_key_count(tg_id)
|
key_count = await get_key_count(tg_id)
|
||||||
balance = await get_balance(tg_id)
|
balance = await get_balance(tg_id)
|
||||||
if balance is None:
|
if balance is None:
|
||||||
balance = 0
|
balance = 0
|
||||||
|
|
||||||
profile_message = profile_message_send(username, tg_id, balance, key_count)
|
profile_message = profile_message_send(username, tg_id, balance, key_count)
|
||||||
|
|
||||||
profile_message += (
|
profile_message += f"<b>Обязательно подпишитесь на канал</b> <a href='{CHANNEL_LINK}'>здесь</a>\n"
|
||||||
f"<b>Обязательно подпишитесь на канал</b> <a href='{CHANNEL_LINK}'>здесь</a>\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
if key_count == 0:
|
if key_count == 0:
|
||||||
profile_message += "\n<i>Нажмите ➕Устройство снизу, чтобы добавить устройство в VPN</i>"
|
profile_message += (
|
||||||
|
"\n<i>Нажмите ➕Устройство снизу, чтобы добавить устройство в VPN</i>"
|
||||||
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
)
|
||||||
[InlineKeyboardButton(text='➕ Устройство', callback_data='create_key'), InlineKeyboardButton(text='📱 Мои устр-ва', callback_data='view_keys')],
|
|
||||||
[InlineKeyboardButton(text='💳 Пополнить баланс', callback_data='pay_freekassa' if PAYMENT_METHOD == 'freekassa' else 'replenish_balance')],
|
inline_keyboard = InlineKeyboardMarkup(
|
||||||
[InlineKeyboardButton(text='👥 Пригласить', callback_data='invite'), InlineKeyboardButton(text='📘 Инструкции', callback_data='instructions')],
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu')]
|
[
|
||||||
])
|
InlineKeyboardButton(
|
||||||
|
text="➕ Устройство", callback_data="create_key"
|
||||||
|
),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="📱 Мои устр-ва", callback_data="view_keys"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="💳 Пополнить баланс",
|
||||||
|
callback_data=(
|
||||||
|
"pay_freekassa"
|
||||||
|
if PAYMENT_METHOD == "freekassa"
|
||||||
|
else "replenish_balance"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(text="👥 Пригласить", callback_data="invite"),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="📘 Инструкции", callback_data="instructions"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# Попробуем удалить предыдущее сообщение
|
# Попробуем удалить предыдущее сообщение
|
||||||
try:
|
try:
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при удалении сообщения: {e}") # Логируем ошибку, если удаление не удалось
|
logger.error(
|
||||||
|
f"Ошибка при удалении сообщения: {e}"
|
||||||
|
) # Логируем ошибку, если удаление не удалось
|
||||||
|
|
||||||
with open(image_path, 'rb') as image_file:
|
with open(image_path, "rb") as image_file:
|
||||||
await bot.send_photo(
|
await bot.send_photo(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id,
|
||||||
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
|
photo=BufferedInputFile(image_file.read(), filename="pic.jpg"),
|
||||||
caption=profile_message,
|
caption=profile_message,
|
||||||
parse_mode='HTML',
|
parse_mode="HTML",
|
||||||
reply_markup=inline_keyboard
|
reply_markup=inline_keyboard,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -75,31 +107,28 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
|
|||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'invite')
|
|
||||||
|
@router.callback_query(lambda c: c.data == "invite")
|
||||||
async def invite_handler(callback_query: types.CallbackQuery):
|
async def invite_handler(callback_query: types.CallbackQuery):
|
||||||
tg_id = callback_query.from_user.id
|
tg_id = callback_query.from_user.id
|
||||||
referral_link = get_referral_link(tg_id)
|
referral_link = get_referral_link(tg_id)
|
||||||
|
|
||||||
referral_stats = await get_referral_stats(tg_id)
|
referral_stats = await get_referral_stats(tg_id)
|
||||||
|
|
||||||
invite_message = (
|
invite_message = invite_message_send(referral_link, referral_stats)
|
||||||
invite_message_send(referral_link,referral_stats)
|
|
||||||
)
|
button_back = InlineKeyboardButton(text="⬅️ Назад", callback_data="view_profile")
|
||||||
|
|
||||||
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='view_profile')
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_back]])
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_back]])
|
||||||
|
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
|
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=tg_id,
|
chat_id=tg_id, text=invite_message, parse_mode="HTML", reply_markup=keyboard
|
||||||
text=invite_message,
|
|
||||||
parse_mode='HTML',
|
|
||||||
reply_markup=keyboard
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'view_profile')
|
|
||||||
|
@router.callback_query(lambda c: c.data == "view_profile")
|
||||||
async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext):
|
async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext):
|
||||||
await process_callback_view_profile(callback_query, state)
|
await process_callback_view_profile(callback_query, state)
|
||||||
|
|||||||
+81
-54
@@ -1,87 +1,113 @@
|
|||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import asyncpg
|
import asyncpg
|
||||||
from aiogram import Router
|
from aiogram import Router
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
from aiogram.types import (BufferedInputFile, CallbackQuery,
|
from aiogram.types import (
|
||||||
InlineKeyboardButton, InlineKeyboardMarkup, Message)
|
BufferedInputFile,
|
||||||
|
CallbackQuery,
|
||||||
|
InlineKeyboardButton,
|
||||||
|
InlineKeyboardMarkup,
|
||||||
|
Message,
|
||||||
|
)
|
||||||
|
|
||||||
from bot import bot
|
from bot import bot
|
||||||
from config import APP_URL, CHANNEL_URL, DATABASE_URL, SUPPORT_CHAT_URL
|
from config import APP_URL, CHANNEL_URL, DATABASE_URL, SUPPORT_CHAT_URL
|
||||||
from database import (add_connection, add_referral, check_connection_exists,
|
from database import add_connection, add_referral, check_connection_exists, get_trial
|
||||||
get_trial)
|
|
||||||
from handlers.keys.trial_key import create_trial_key
|
from handlers.keys.trial_key import create_trial_key
|
||||||
from handlers.texts import ABOUT_VPN, INSTRUCTIONS_TRIAL, WELCOME_TEXT
|
from handlers.texts import ABOUT_VPN, INSTRUCTIONS_TRIAL, WELCOME_TEXT
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
class FeedbackState(StatesGroup):
|
class FeedbackState(StatesGroup):
|
||||||
waiting_for_feedback = State()
|
waiting_for_feedback = State()
|
||||||
|
|
||||||
|
|
||||||
async def send_welcome_message(chat_id: int, trial_status: int):
|
async def send_welcome_message(chat_id: int, trial_status: int):
|
||||||
welcome_text = WELCOME_TEXT
|
welcome_text = WELCOME_TEXT
|
||||||
|
|
||||||
image_path = os.path.join(os.path.dirname(__file__), 'pic.jpg')
|
image_path = os.path.join(os.path.dirname(__file__), "pic.jpg")
|
||||||
|
|
||||||
if not os.path.isfile(image_path):
|
if not os.path.isfile(image_path):
|
||||||
await bot.send_message(chat_id, "Файл изображения не найден.")
|
await bot.send_message(chat_id, "Файл изображения не найден.")
|
||||||
return
|
return
|
||||||
|
|
||||||
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
inline_keyboard = InlineKeyboardMarkup(
|
||||||
[InlineKeyboardButton(text='🔗 Подключить VPN', callback_data='connect_vpn')] if trial_status == 0 else [],
|
inline_keyboard=[
|
||||||
[InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')],
|
(
|
||||||
[InlineKeyboardButton(text='🔒 О VPN', callback_data='about_vpn')],
|
[
|
||||||
[InlineKeyboardButton(text='📞 Поддержка', url=SUPPORT_CHAT_URL)],
|
InlineKeyboardButton(
|
||||||
[InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL)],
|
text="🔗 Подключить VPN", callback_data="connect_vpn"
|
||||||
])
|
)
|
||||||
|
]
|
||||||
|
if trial_status == 0
|
||||||
|
else []
|
||||||
|
),
|
||||||
|
[InlineKeyboardButton(text="👤 Мой профиль", callback_data="view_profile")],
|
||||||
|
[InlineKeyboardButton(text="🔒 О VPN", callback_data="about_vpn")],
|
||||||
|
[InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)],
|
||||||
|
[InlineKeyboardButton(text="📢 Наш канал", url=CHANNEL_URL)],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
inline_keyboard.inline_keyboard = [row for row in inline_keyboard.inline_keyboard if row]
|
inline_keyboard.inline_keyboard = [
|
||||||
|
row for row in inline_keyboard.inline_keyboard if row
|
||||||
|
]
|
||||||
|
|
||||||
with open(image_path, 'rb') as image_from_buffer:
|
with open(image_path, "rb") as image_from_buffer:
|
||||||
await bot.send_photo(
|
await bot.send_photo(
|
||||||
chat_id,
|
chat_id,
|
||||||
BufferedInputFile(image_from_buffer.read(), filename="pic.jpg"),
|
BufferedInputFile(image_from_buffer.read(), filename="pic.jpg"),
|
||||||
caption=welcome_text,
|
caption=welcome_text,
|
||||||
parse_mode='HTML',
|
parse_mode="HTML",
|
||||||
reply_markup=inline_keyboard
|
reply_markup=inline_keyboard,
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.message(Command('start'))
|
|
||||||
|
@router.message(Command("start"))
|
||||||
async def start_command(message: Message):
|
async def start_command(message: Message):
|
||||||
print(f"Received start command with text: {message.text}")
|
logging.info(f"Received start command with text: {message.text}")
|
||||||
if 'referral_' in message.text:
|
if "referral_" in message.text:
|
||||||
referrer_tg_id = int(message.text.split('referral_')[1])
|
referrer_tg_id = int(message.text.split("referral_")[1])
|
||||||
print(f"Referral ID: {referrer_tg_id}")
|
logging.info(f"Referral ID: {referrer_tg_id}")
|
||||||
if not await check_connection_exists(message.from_user.id):
|
if not await check_connection_exists(message.from_user.id):
|
||||||
await add_connection(message.from_user.id)
|
await add_connection(message.from_user.id)
|
||||||
await add_referral(message.from_user.id, referrer_tg_id)
|
await add_referral(message.from_user.id, referrer_tg_id)
|
||||||
await message.answer("Вас пригласил друг, добро пожаловать!")
|
await message.answer("Вас пригласил друг, добро пожаловать!")
|
||||||
else:
|
else:
|
||||||
await message.answer("Вы уже зарегистрированы в системе!")
|
await message.answer("Вы уже зарегистрированы в системе!")
|
||||||
|
|
||||||
trial_status = await get_trial(message.from_user.id)
|
trial_status = await get_trial(message.from_user.id)
|
||||||
await send_welcome_message(message.chat.id, trial_status)
|
await send_welcome_message(message.chat.id, trial_status)
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'connect_vpn')
|
|
||||||
|
@router.callback_query(lambda c: c.data == "connect_vpn")
|
||||||
async def handle_connect_vpn(callback_query: CallbackQuery):
|
async def handle_connect_vpn(callback_query: CallbackQuery):
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
user_id = callback_query.from_user.id
|
user_id = callback_query.from_user.id
|
||||||
|
|
||||||
trial_key_info = await create_trial_key(user_id)
|
trial_key_info = await create_trial_key(user_id)
|
||||||
|
|
||||||
if 'error' in trial_key_info:
|
if "error" in trial_key_info:
|
||||||
await callback_query.message.answer(trial_key_info['error'])
|
await callback_query.message.answer(trial_key_info["error"])
|
||||||
else:
|
else:
|
||||||
conn = await asyncpg.connect(DATABASE_URL)
|
conn = await asyncpg.connect(DATABASE_URL)
|
||||||
try:
|
try:
|
||||||
result = await conn.execute('''
|
result = await conn.execute(
|
||||||
|
"""
|
||||||
UPDATE connections SET trial = 1 WHERE tg_id = $1
|
UPDATE connections SET trial = 1 WHERE tg_id = $1
|
||||||
''', user_id)
|
""",
|
||||||
print(f"Rows updated: {result}")
|
user_id,
|
||||||
|
)
|
||||||
|
logging.info(f"Rows updated: {result}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при обновлении trial: {e}")
|
logging.error(f"Ошибка при обновлении trial: {e}")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
await conn.close()
|
await conn.close()
|
||||||
@@ -91,58 +117,59 @@ async def handle_connect_vpn(callback_query: CallbackQuery):
|
|||||||
f"<b>Инструкции:</b>\n{INSTRUCTIONS_TRIAL}"
|
f"<b>Инструкции:</b>\n{INSTRUCTIONS_TRIAL}"
|
||||||
)
|
)
|
||||||
|
|
||||||
button_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')
|
button_profile = InlineKeyboardButton(
|
||||||
|
text="👤 Мой профиль", callback_data="view_profile"
|
||||||
|
)
|
||||||
|
|
||||||
button_iphone = InlineKeyboardButton(
|
button_iphone = InlineKeyboardButton(
|
||||||
text='🍏 Подключить',
|
text="🍏 Подключить",
|
||||||
url=f'{APP_URL}/?url=v2raytun://import/{trial_key_info["key"]}'
|
url=f'{APP_URL}/?url=v2raytun://import/{trial_key_info["key"]}',
|
||||||
)
|
)
|
||||||
button_android = InlineKeyboardButton(
|
button_android = InlineKeyboardButton(
|
||||||
text='🤖 Подключить',
|
text="🤖 Подключить",
|
||||||
url=f'{APP_URL}/?url=v2raytun://import-sub?url={trial_key_info["key"]}'
|
url=f'{APP_URL}/?url=v2raytun://import-sub?url={trial_key_info["key"]}',
|
||||||
)
|
)
|
||||||
|
|
||||||
button_download_iphone = InlineKeyboardButton(
|
button_download_iphone = InlineKeyboardButton(
|
||||||
text='🍏 Скачать',
|
text="🍏 Скачать", url="https://apps.apple.com/ru/app/v2raytun/id6476628951"
|
||||||
url='https://apps.apple.com/ru/app/v2raytun/id6476628951'
|
|
||||||
)
|
)
|
||||||
button_download_android = InlineKeyboardButton(
|
button_download_android = InlineKeyboardButton(
|
||||||
text='🤖 Скачать',
|
text="🤖 Скачать",
|
||||||
url='https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru'
|
url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru",
|
||||||
)
|
)
|
||||||
|
|
||||||
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
inline_keyboard = InlineKeyboardMarkup(
|
||||||
[button_download_iphone, button_download_android],
|
inline_keyboard=[
|
||||||
[button_iphone, button_android],
|
[button_download_iphone, button_download_android],
|
||||||
[button_profile]
|
[button_iphone, button_android],
|
||||||
])
|
[button_profile],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
await callback_query.message.answer(
|
await callback_query.message.answer(
|
||||||
key_message,
|
key_message, parse_mode="HTML", reply_markup=inline_keyboard
|
||||||
parse_mode='HTML',
|
|
||||||
reply_markup=inline_keyboard
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'about_vpn')
|
|
||||||
|
@router.callback_query(lambda c: c.data == "about_vpn")
|
||||||
async def handle_about_vpn(callback_query: CallbackQuery):
|
async def handle_about_vpn(callback_query: CallbackQuery):
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
|
|
||||||
bot_version = "3.0.1_beta"
|
bot_version = "3.0.1_beta"
|
||||||
info_message = ABOUT_VPN.format(bot_version=bot_version)
|
info_message = ABOUT_VPN.format(bot_version=bot_version)
|
||||||
|
|
||||||
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu')
|
button_back = InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu")
|
||||||
inline_keyboard_back = InlineKeyboardMarkup(inline_keyboard=[[button_back]])
|
inline_keyboard_back = InlineKeyboardMarkup(inline_keyboard=[[button_back]])
|
||||||
|
|
||||||
await callback_query.message.answer(
|
await callback_query.message.answer(
|
||||||
info_message,
|
info_message, parse_mode="HTML", reply_markup=inline_keyboard_back
|
||||||
parse_mode='HTML',
|
|
||||||
reply_markup=inline_keyboard_back
|
|
||||||
)
|
)
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@router.callback_query(lambda c: c.data == 'back_to_menu')
|
|
||||||
|
@router.callback_query(lambda c: c.data == "back_to_menu")
|
||||||
async def handle_back_to_menu(callback_query: CallbackQuery):
|
async def handle_back_to_menu(callback_query: CallbackQuery):
|
||||||
await callback_query.message.delete()
|
await callback_query.message.delete()
|
||||||
trial_status = await get_trial(callback_query.from_user.id)
|
trial_status = await get_trial(callback_query.from_user.id)
|
||||||
|
|||||||
+11
-7
@@ -5,23 +5,27 @@ from config import SERVERS
|
|||||||
|
|
||||||
|
|
||||||
def sanitize_key_name(key_name: str) -> str:
|
def sanitize_key_name(key_name: str) -> str:
|
||||||
return re.sub(r'[^a-z0-9@._-]', '', key_name.lower())
|
return re.sub(r"[^a-z0-9@._-]", "", key_name.lower())
|
||||||
|
|
||||||
|
|
||||||
def generate_random_email():
|
def generate_random_email():
|
||||||
"""Генерирует случайный набор символов."""
|
"""Генерирует случайный набор символов."""
|
||||||
random_string = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=6))
|
random_string = "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=6))
|
||||||
return random_string
|
return random_string
|
||||||
|
|
||||||
|
|
||||||
async def get_least_loaded_server(conn):
|
async def get_least_loaded_server(conn):
|
||||||
"""Находит сервер с наименьшей загрузкой."""
|
"""Находит сервер с наименьшей загрузкой."""
|
||||||
least_loaded_server_id = None
|
least_loaded_server_id = None
|
||||||
min_load_percentage = float('inf')
|
min_load_percentage = float("inf")
|
||||||
|
|
||||||
for server_id, server in SERVERS.items():
|
for server_id, server in SERVERS.items():
|
||||||
count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id)
|
count = await conn.fetchval(
|
||||||
percent_full = (count / 60) * 100 if count <= 60 else 100
|
"SELECT COUNT(*) FROM keys WHERE server_id = $1", server_id
|
||||||
|
)
|
||||||
|
percent_full = (count / 60) * 100 if count <= 60 else 100
|
||||||
if percent_full < min_load_percentage:
|
if percent_full < min_load_percentage:
|
||||||
min_load_percentage = percent_full
|
min_load_percentage = percent_full
|
||||||
least_loaded_server_id = server_id
|
least_loaded_server_id = server_id
|
||||||
|
|
||||||
return least_loaded_server_id
|
return least_loaded_server_id
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
from functools import wraps
|
||||||
|
from typing import Any, Awaitable, Callable, Dict
|
||||||
|
|
||||||
|
from aiogram import BaseMiddleware
|
||||||
|
from aiogram.types import CallbackQuery, Message, TelegramObject
|
||||||
|
|
||||||
|
from config import ADMIN_ID
|
||||||
|
|
||||||
|
|
||||||
|
class AdminMiddleware(BaseMiddleware):
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
|
||||||
|
event: TelegramObject,
|
||||||
|
data: Dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
# Проверяем, является ли пользователь администратором
|
||||||
|
user_id = None
|
||||||
|
|
||||||
|
if isinstance(event, Message):
|
||||||
|
user_id = event.from_user.id
|
||||||
|
elif isinstance(event, CallbackQuery):
|
||||||
|
user_id = event.from_user.id
|
||||||
|
|
||||||
|
if user_id not in int(ADMIN_ID):
|
||||||
|
data["is_admin"] = False
|
||||||
|
else:
|
||||||
|
data["is_admin"] = True
|
||||||
|
|
||||||
|
return await handler(event, data)
|
||||||
|
|
||||||
|
|
||||||
|
def admin_only():
|
||||||
|
def decorator(func):
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
# Извлекаем объект события (Message или CallbackQuery)
|
||||||
|
event = args[0]
|
||||||
|
|
||||||
|
# Определяем ID пользователя
|
||||||
|
user_id = event.from_user.id if hasattr(event, "from_user") else None
|
||||||
|
|
||||||
|
if user_id not in int(ADMIN_ID):
|
||||||
|
# Можно отправить сообщение или просто return
|
||||||
|
if isinstance(event, Message):
|
||||||
|
await event.answer("У вас нет доступа к этой команде.")
|
||||||
|
elif isinstance(event, CallbackQuery):
|
||||||
|
await event.answer(
|
||||||
|
"У вас нет доступа к этому действию.", show_alert=True
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
Reference in New Issue
Block a user