coupons initial

This commit is contained in:
Vladless
2024-11-15 03:03:24 +03:00
parent 454b839fae
commit 3adc7fc59f
8 changed files with 455 additions and 40 deletions
+4 -2
View File
@@ -10,15 +10,17 @@ storage = MemoryStorage()
dp = Dispatcher(bot=bot, storage=storage)
router = Router()
from handlers import commands, notifications, pay, profile, start
from handlers.admin import admin_commands, admin_panel, admin_user_editor
from handlers import commands, coupons, notifications, pay, profile, start
from handlers.admin import admin_commands, admin_coupons, admin_panel, admin_user_editor
from handlers.keys import key_management, keys
from handlers.payments import cryprobot_pay, freekassa_pay, stars_pay, yookassa_pay
dp.include_router(admin_commands.router)
dp.include_router(admin_coupons.router)
dp.include_router(admin_panel.router)
dp.include_router(admin_user_editor.router)
dp.include_router(commands.router)
dp.include_router(coupons.router)
dp.include_router(start.router)
dp.include_router(profile.router)
dp.include_router(keys.router)
+101 -30
View File
@@ -9,6 +9,7 @@ from config import BONUS_PERCENT, DATABASE_URL
async def init_db():
conn = await asyncpg.connect(DATABASE_URL)
# Таблица для хранения информации о пользователях
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS connections (
@@ -16,9 +17,10 @@ async def init_db():
balance REAL NOT NULL DEFAULT 0.0,
trial INTEGER NOT NULL DEFAULT 0
)
"""
"""
)
# Таблица для хранения ключей
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS keys (
@@ -28,54 +30,123 @@ async def init_db():
created_at BIGINT NOT NULL,
expiry_time BIGINT NOT NULL,
key TEXT NOT NULL,
server_id TEXT NOT NULL DEFAULT 'server1', -- поле для идентификатора сервера
notified BOOLEAN NOT NULL DEFAULT FALSE, -- новое поле для статуса уведомления
server_id TEXT NOT NULL DEFAULT 'cluster1',
notified BOOLEAN NOT NULL DEFAULT FALSE,
notified_24h BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (tg_id, client_id)
)
"""
"""
)
# Таблица для хранения рефералов
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS referrals (
referred_tg_id BIGINT PRIMARY KEY NOT NULL, -- ID приглашенного пользователя
referrer_tg_id BIGINT NOT NULL, -- ID пригласившего пользователя
reward_issued BOOLEAN DEFAULT FALSE -- Был ли начислен бонус
referred_tg_id BIGINT PRIMARY KEY NOT NULL,
referrer_tg_id BIGINT NOT NULL,
reward_issued BOOLEAN DEFAULT FALSE
)
"""
"""
)
try:
await conn.execute(
"""
ALTER TABLE keys
ADD COLUMN server_id TEXT NOT NULL DEFAULT 'server1'
# Таблица для хранения купонов
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS coupons (
id SERIAL PRIMARY KEY,
code TEXT UNIQUE NOT NULL,
amount INTEGER NOT NULL,
usage_limit INTEGER NOT NULL DEFAULT 1,
usage_count INTEGER NOT NULL DEFAULT 0,
is_used BOOLEAN NOT NULL DEFAULT FALSE
)
except asyncpg.exceptions.DuplicateColumnError:
pass
"""
)
try:
await conn.execute(
"""
ALTER TABLE keys
ADD COLUMN notified BOOLEAN NOT NULL DEFAULT FALSE
# Таблица для отслеживания использований купонов пользователями
await conn.execute(
"""
CREATE TABLE IF NOT EXISTS coupon_usages (
coupon_id INTEGER NOT NULL REFERENCES coupons(id) ON DELETE CASCADE,
user_id BIGINT NOT NULL,
used_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (coupon_id, user_id)
)
except asyncpg.exceptions.DuplicateColumnError:
pass
try:
await conn.execute(
"""
ALTER TABLE keys
ADD COLUMN notified_24h BOOLEAN NOT NULL DEFAULT FALSE
"""
)
except asyncpg.exceptions.DuplicateColumnError:
pass
)
await conn.close()
async def create_coupon(coupon_code: str, amount: float, usage_limit: int):
conn = await asyncpg.connect(DATABASE_URL)
try:
await conn.execute(
"""
INSERT INTO coupons (code, amount, usage_limit, usage_count, is_used)
VALUES ($1, $2, $3, 0, FALSE)
""",
coupon_code,
amount,
usage_limit,
)
except Exception as e:
print(f"Ошибка при создании купона: {e}")
raise
finally:
await conn.close()
async def get_all_coupons():
"""Получить список всех купонов"""
conn = await asyncpg.connect(DATABASE_URL)
try:
coupons = await conn.fetch(
"""
SELECT code, amount, usage_limit, usage_count
FROM coupons
"""
)
return coupons
except Exception as e:
print(f"Ошибка при получении купонов: {e}")
return []
finally:
await conn.close()
async def delete_coupon_from_db(coupon_code: str):
"""Удалить купон из базы данных по его коду"""
conn = await asyncpg.connect(DATABASE_URL)
try:
coupon_record = await conn.fetchrow(
"""
SELECT id FROM coupons WHERE code = $1
""",
coupon_code,
)
if not coupon_record:
return False
await conn.execute(
"""
DELETE FROM coupons WHERE code = $1
""",
coupon_code,
)
return True
except Exception as e:
print(f"Ошибка при удалении купона: {e}")
return False
finally:
await conn.close()
async def restore_trial(tg_id: int):
conn = await asyncpg.connect(DATABASE_URL)
try:
+207
View File
@@ -0,0 +1,207 @@
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from loguru import logger
from database import create_coupon, delete_coupon_from_db, get_all_coupons
class AdminCouponsState(StatesGroup):
waiting_for_coupon_data = State()
router = Router()
@router.callback_query(F.data == "coupons_editor")
async def show_coupon_management_menu(callback_query: types.CallbackQuery):
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text=" Создать купон", callback_data="create_coupon")
)
builder.row(InlineKeyboardButton(text="Купоны", callback_data="coupons"))
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_admin_menu")
)
markup = builder.as_markup()
await callback_query.message.answer(
"🛠 Меню управления купонами:", reply_markup=markup
)
await callback_query.answer()
@router.callback_query(F.data == "coupons")
async def show_coupon_list(callback_query: types.CallbackQuery):
try:
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
coupons = await get_all_coupons()
if not coupons:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
)
markup = builder.as_markup()
await callback_query.message.answer(
"❌ На данный момент нет доступных купонов.\n"
"Вы можете вернуться в меню управления.",
parse_mode="HTML",
reply_markup=markup,
)
await callback_query.answer()
return
coupon_list = "📜 Список всех купонов:\n\n"
builder = InlineKeyboardBuilder()
for coupon in coupons:
coupon_list += (
f"<b>Код:</b> {coupon['code']}\n"
f"<b>Сумма:</b> {coupon['amount']} рублей\n"
f"<b>Лимит использования:</b> {coupon['usage_limit']} раз\n"
f"<b>Использовано:</b> {coupon['usage_count']} раз\n\n"
)
builder.row(
InlineKeyboardButton(
text=f"❌ Удалить {coupon['code']}",
callback_data=f"delete_coupon_{coupon['code']}",
)
)
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
)
markup = builder.as_markup()
await callback_query.message.answer(
coupon_list, parse_mode="HTML", reply_markup=markup
)
except Exception as e:
logger.error(f"Ошибка при получении списка купонов: {e}")
await callback_query.message.answer(
f"❌ Произошла ошибка при получении списка купонов: {e}", parse_mode="HTML"
)
await callback_query.answer()
@router.callback_query(F.data.startswith("delete_coupon_"))
async def handle_delete_coupon(callback_query: types.CallbackQuery):
coupon_code = callback_query.data[len("delete_coupon_") :]
try:
result = await delete_coupon_from_db(coupon_code)
if result:
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await show_coupon_list(callback_query)
else:
await callback_query.message.answer(
f"❌ Купон с кодом <b>{coupon_code}</b> не найден.", parse_mode="HTML"
)
await show_coupon_list(callback_query)
except Exception as e:
logger.error(f"Ошибка при удалении купона: {e}")
await callback_query.message.answer(
f"❌ Произошла ошибка при удалении купона: {e}", parse_mode="HTML"
)
await callback_query.answer()
@router.callback_query(F.data == "create_coupon")
async def handle_create_coupon(callback_query: types.CallbackQuery, state: FSMContext):
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await callback_query.message.answer(
"<b>Введите данные для создания купона в формате:</b>\n\n"
"<i>код</i> <i>сумма</i> <i>лимит</i>\n\n"
"Пример: <b>'COUPON1 50 5'</b>\n\n",
parse_mode="HTML",
)
await state.set_state(AdminCouponsState.waiting_for_coupon_data)
await callback_query.answer()
@router.message(AdminCouponsState.waiting_for_coupon_data)
async def process_coupon_data(message: types.Message, state: FSMContext):
text = message.text.strip()
parts = text.split()
if len(parts) != 3:
await message.answer(
"<b>❌ Некорректный формат!</b> Пожалуйста, введите данные в формате:\n"
"<b>код</b> <b>сумма</b> <b>лимит</b>\n"
"Пример: <b>'COUPON1 50 5'</b>",
parse_mode="HTML",
)
return
try:
coupon_code = parts[0]
coupon_amount = float(parts[1])
usage_limit = int(parts[2])
except ValueError:
await message.answer(
"<b>⚠️ Проверьте правильность введенных данных.</b>\n"
"Сумма должна быть числом, а лимит — целым числом.",
parse_mode="HTML",
)
return
try:
await create_coupon(coupon_code, coupon_amount, usage_limit)
result_message = (
f"✅ Купон с кодом <b>{coupon_code}</b> успешно создан!\n"
f"Сумма: <b>{coupon_amount} рублей</b>\n"
f"Лимит использования: <b>{usage_limit} раз</b>."
)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
)
markup = builder.as_markup()
try:
await message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await message.answer(result_message, parse_mode="HTML", reply_markup=markup)
await state.clear()
except Exception as e:
logger.error(f"Ошибка при создании купона: {e}")
await message.answer(
f"<b>❌ Ошибка при создании купона:</b> {e}", parse_mode="HTML"
)
@router.callback_query(F.data == "back_to_coupons_menu")
async def back_to_coupons_menu(callback_query: types.CallbackQuery):
"""Возвращаем пользователя в меню управления купонами"""
await show_coupon_management_menu(callback_query)
+10
View File
@@ -36,6 +36,11 @@ async def handle_admin_command(message: types.Message):
text="👥 Управление пользователями", callback_data="user_editor"
)
)
builder.row(
InlineKeyboardButton(
text="🎟️ Управление купонами", callback_data="coupons_editor"
)
)
builder.row(
InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls")
)
@@ -164,6 +169,11 @@ async def back_to_admin_menu(callback_query: CallbackQuery):
builder.row(
InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls")
)
builder.row(
InlineKeyboardButton(
text="🎟️ Управление купонами", callback_data="coupons_editor"
)
)
builder.row(
InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups")
)
+122
View File
@@ -0,0 +1,122 @@
from datetime import datetime
import asyncpg
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from loguru import logger
from config import DATABASE_URL
from database import update_balance
class CouponActivationState(StatesGroup):
waiting_for_coupon_code = State()
router = Router()
@router.callback_query(F.data == "activate_coupon")
async def handle_activate_coupon(
callback_query: types.CallbackQuery, state: FSMContext
):
try:
await callback_query.message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
await callback_query.message.answer(
"<b>Введите код купона:</b>\n\n"
"Пожалуйста, введите действующий код купона, который вы хотите активировать.",
parse_mode="HTML",
)
await state.set_state(CouponActivationState.waiting_for_coupon_code)
await callback_query.answer()
@router.message(CouponActivationState.waiting_for_coupon_code)
async def process_coupon_code(message: types.Message, state: FSMContext):
try:
await message.delete()
except Exception as e:
logger.error(f"Ошибка при удалении сообщения: {e}")
coupon_code = message.text.strip()
activation_result = await activate_coupon(message.from_user.id, coupon_code)
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="view_profile")
)
markup = builder.as_markup()
await message.answer(activation_result, reply_markup=markup, parse_mode="HTML")
await state.clear()
async def activate_coupon(user_id: int, coupon_code: str):
"""Функция для активации купона"""
conn = await asyncpg.connect(DATABASE_URL)
try:
coupon_record = await conn.fetchrow(
"""
SELECT id, usage_limit, usage_count, is_used, amount
FROM coupons
WHERE code = $1 AND (usage_count < usage_limit OR usage_limit = 0) AND is_used = FALSE
""",
coupon_code,
)
if not coupon_record:
return "<b>❌ Купон не найден</b> или его использование ограничено. Пожалуйста, проверьте код и попробуйте снова."
usage_exists = await conn.fetchrow(
"""
SELECT 1 FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2
""",
coupon_record["id"],
user_id,
)
if usage_exists:
return "<b>❌ Вы уже активировали этот купон.</b> Купоны могут быть активированы только один раз."
coupon_amount = coupon_record["amount"]
async with conn.transaction():
await conn.execute(
"""
UPDATE coupons
SET usage_count = usage_count + 1,
is_used = CASE WHEN usage_count + 1 >= usage_limit AND usage_limit > 0 THEN TRUE ELSE FALSE END
WHERE id = $1
""",
coupon_record["id"],
)
await conn.execute(
"""
INSERT INTO coupon_usages (coupon_id, user_id, used_at)
VALUES ($1, $2, $3)
""",
coupon_record["id"],
user_id,
datetime.utcnow(),
)
await update_balance(user_id, coupon_amount)
return f"<b>✅ Купон успешно активирован!</b>\n\nНа ваш баланс добавлено <b>{coupon_amount} рублей</b>."
except Exception as e:
logger.error(f"Ошибка при активации купона: {e}")
return (
"<b>⚠️ Произошла ошибка при активации купона.</b>\nПопробуйте ещё раз позже."
)
finally:
await conn.close()
+6 -1
View File
@@ -17,7 +17,7 @@ async def handle_pay(callback_query: CallbackQuery):
if YOOKASSA_ENABLE:
builder.row(
InlineKeyboardButton(
text="💳 ЯКасса: быстрый перевод",
text="💳 ЮКасса: быстрый перевод",
callback_data="pay_yookassa",
)
)
@@ -42,6 +42,11 @@ async def handle_pay(callback_query: CallbackQuery):
callback_data="pay_stars",
)
)
builder.row(
InlineKeyboardButton(
text="🎟️ Активировать купон", callback_data="activate_coupon"
)
)
builder.row(
InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile")
+4 -6
View File
@@ -83,13 +83,11 @@ async def process_callback_pay_yookassa(
)
)
builder.row(
InlineKeyboardButton(
text="💰 Ввести свою сумму", callback_data="enter_custom_amount"
)
)
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile")
InlineKeyboardButton(
text="💰 Ввести свою сумму", callback_data="enter_custom_amount"
)
)
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
key_count = await get_key_count(tg_id)
+1 -1
View File
@@ -133,7 +133,7 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session):
async def handle_about_vpn(callback_query: CallbackQuery):
await callback_query.message.delete()
about_vpn_message = get_about_vpn("3.0.5_preStable")
about_vpn_message = get_about_vpn("3.0.6_preStable")
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_menu"))