adding servers for special groups/ ruff formatting

This commit is contained in:
Vladless
2025-10-11 01:27:10 +03:00
parent 7488eac897
commit 97b582d02e
13 changed files with 406 additions and 45 deletions
+13
View File
@@ -111,6 +111,7 @@ class Server(DictLikeMixin, Base):
enabled = Column(Boolean, default=True)
subgroups = relationship("ServerSubgroup", back_populates="server", cascade="all, delete-orphan")
groups = relationship("ServerSpecialgroup", back_populates="server", cascade="all, delete-orphan")
class ServerSubgroup(DictLikeMixin, Base):
@@ -126,6 +127,18 @@ class ServerSubgroup(DictLikeMixin, Base):
__table_args__ = (UniqueConstraint("server_id", "subgroup_title", name="uq_server_subgroup"),)
class ServerSpecialgroup(DictLikeMixin, Base):
__tablename__ = "server_specialgroups"
id = Column(Integer, primary_key=True, autoincrement=True)
server_id = Column(Integer, ForeignKey("servers.id", ondelete="CASCADE"), index=True, nullable=False)
group_code = Column(String, nullable=False)
server = relationship("Server")
__table_args__ = (UniqueConstraint("server_id", "group_code", name="uq_server_group"),)
class Payment(DictLikeMixin, Base):
__tablename__ = "payments"
+15 -1
View File
@@ -2,7 +2,7 @@ from sqlalchemy import delete, func, insert, select, update
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Key, Server, ServerSubgroup, Tariff
from database.models import Key, Server, ServerSpecialgroup, ServerSubgroup, Tariff
from logger import logger
@@ -58,11 +58,24 @@ async def get_servers(session: AsyncSession, include_enabled: bool = False) -> d
for sid, sg in r.all():
subs_map.setdefault(sid, []).append(sg)
groups_map = {}
if ids:
r2 = await session.execute(
select(ServerSpecialgroup.server_id, ServerSpecialgroup.group_code).where(
ServerSpecialgroup.server_id.in_(ids)
)
)
for sid, gc in r2.all():
groups_map.setdefault(sid, []).append(gc)
allowed = {"trial", "discounts", "discounts_max"}
grouped = {}
for s in servers:
if not include_enabled and not s.enabled:
continue
cluster = s.cluster_name
special = sorted({g for g in groups_map.get(s.id, []) if g in allowed})
grouped.setdefault(cluster, []).append({
"server_name": s.server_name,
"api_url": s.api_url,
@@ -73,6 +86,7 @@ async def get_servers(session: AsyncSession, include_enabled: bool = False) -> d
"max_keys": s.max_keys,
"tariff_group": s.tariff_group,
"tariff_subgroups": subs_map.get(s.id, []),
"special_groups": special,
"cluster_name": cluster,
})
return grouped
+224 -4
View File
@@ -8,7 +8,7 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message
from py3xui import AsyncApi
from sqlalchemy import delete, func, select, update
from sqlalchemy import and_, delete, func, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from config import (
@@ -19,7 +19,7 @@ from config import (
USE_COUNTRY_SELECTION,
)
from database import check_unique_server_name, get_servers, update_key_expiry
from database.models import Key, Server, ServerSubgroup, Tariff
from database.models import Key, Server, ServerSpecialgroup, ServerSubgroup, Tariff
from filters.admin import IsAdminFilter
from handlers.keys.operations import (
create_client_on_server,
@@ -27,6 +27,7 @@ from handlers.keys.operations import (
delete_key_from_cluster,
renew_key_in_cluster,
)
from handlers.utils import ALLOWED_GROUP_CODES
from logger import logger
from panels.remnawave import RemnawaveAPI
from utils.backup import create_backup_and_send_to_admins
@@ -35,12 +36,15 @@ from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb
from .keyboard import (
AdminClusterCallback,
AdminServerCallback,
build_attach_tariff_kb,
build_cluster_management_kb,
build_clusters_editor_kb,
build_manage_cluster_kb,
build_panel_type_kb,
build_select_group_servers_kb,
build_select_subgroup_servers_kb,
build_sync_cluster_kb,
build_tariff_group_selection_for_servers_kb,
build_tariff_group_selection_kb,
build_tariff_subgroup_selection_kb,
)
@@ -322,16 +326,24 @@ async def handle_cluster_servers(callback: CallbackQuery, session: AsyncSession)
servers = await get_servers(session=session, include_enabled=True)
cluster_servers = servers.get(cluster_name, [])
allowed = {"trial", "discounts", "discounts_max"}
lines = []
for s in cluster_servers:
subs = s.get("tariff_subgroups") or []
subs_str = ", ".join(sorted(subs)) if subs else ""
lines.append(f"{s.get('server_name', '?')}{subs_str}")
grps = s.get("special_groups") or []
grps = [g for g in grps if g in allowed]
grps_str = ", ".join(sorted(grps)) if grps else ""
lines.append(f"{s.get('server_name', '?')}{subs_str} | {grps_str}")
details = "\n".join(lines) if lines else "нет серверов"
await callback.message.edit_text(
text=(f"<b>📡 Серверы в кластере {cluster_name}</b>\n<i>подгруппы:</i>\n<blockquote>{details}</blockquote>"),
text=(
f"<b>📡 Серверы в кластере {cluster_name}</b>\n<i>подгруппы | спецгруппы:</i>\n<blockquote>{details}</blockquote>"
),
reply_markup=build_manage_cluster_kb(cluster_servers, cluster_name),
)
@@ -1309,3 +1321,211 @@ async def reset_cluster_subgroups(callback: CallbackQuery, callback_data: AdminC
except Exception as e:
logger.error(f"Ошибка при сбросе подгрупп для кластера {cluster_name}: {e}")
await callback.message.edit_text("❌ Не удалось сбросить подгруппы.")
def render_attach_tariff_menu_text(cluster_name: str, cluster_servers: list[dict]) -> str:
sub_map: dict[str, list[str]] = {}
for s in cluster_servers:
for sg in s.get("tariff_subgroups") or []:
sub_map.setdefault(sg, []).append(s["server_name"])
allowed = ("trial", "discounts", "discounts_max")
spec_map: dict[str, list[str]] = {k: [] for k in allowed}
for s in cluster_servers:
for g in s.get("special_groups") or []:
if g in spec_map:
spec_map[g].append(s["server_name"])
lines = [f"<b>🧩 Привязки тарифов • {cluster_name}</b>"]
lines.append("<b>Подгруппы:</b>")
if sub_map:
subs_lines = []
for k in sorted(sub_map):
servers_list = ", ".join(sorted(set(sub_map[k])))
subs_lines.append(f"• <b>{k}</b>: {servers_list}")
lines.append("<blockquote>\n" + "\n".join(subs_lines) + "\n</blockquote>")
else:
lines.append("<blockquote>— нет привязок</blockquote>")
lines.append("<b>Спецгруппы:</b>")
has_spec = any(spec_map[k] for k in allowed)
if has_spec:
spec_lines = []
for k in allowed:
vals = sorted(set(spec_map[k]))
spec_lines.append(f"• <b>{k}</b>: {', '.join(vals) if vals else ''}")
lines.append("<blockquote>\n" + "\n".join(spec_lines) + "\n</blockquote>")
else:
lines.append("<blockquote>— нет привязок</blockquote>")
return "\n".join(lines)
@router.callback_query(AdminClusterCallback.filter(F.action == "attach_tariff_menu"), IsAdminFilter())
async def handle_attach_tariff_menu(callback: CallbackQuery, session: AsyncSession):
packed = AdminClusterCallback.unpack(callback.data)
cluster_name = packed.data
servers = await get_servers(session, include_enabled=True)
cluster_servers = servers.get(cluster_name, [])
text = render_attach_tariff_menu_text(cluster_name, cluster_servers)
await callback.message.edit_text(
text=text,
reply_markup=build_attach_tariff_kb(cluster_name),
disable_web_page_preview=True,
)
@router.callback_query(AdminClusterCallback.filter(F.action == "set_group"))
async def show_servers_for_group(
callback: CallbackQuery, callback_data: AdminClusterCallback, session: AsyncSession, state: FSMContext
):
cluster_name = callback_data.data
servers = await get_servers(session=session, include_enabled=True)
cluster_servers = servers.get(cluster_name, [])
data = await state.get_data()
selected = set(data.get(f"grp_sel:{cluster_name}", []))
await callback.message.edit_text(
f"<b>🗂 Выберите серверы в кластере <code>{cluster_name}</code> для назначения тарифной группы:</b>",
reply_markup=build_select_group_servers_kb(cluster_name, cluster_servers, selected),
)
@router.callback_query(AdminClusterCallback.filter(F.action == "toggle_server_group"))
async def toggle_server_for_group(
callback: CallbackQuery, callback_data: AdminClusterCallback, session: AsyncSession, state: FSMContext
):
cluster_name, idx_str = callback_data.data.split("|", 1)
i = int(idx_str)
servers = await get_servers(session=session, include_enabled=True)
cluster_servers = servers.get(cluster_name, [])
names = []
for s in cluster_servers:
if isinstance(s, str):
names.append(s)
elif isinstance(s, dict):
names.append(s.get("server_name") or s.get("name") or str(s))
else:
names.append(getattr(s, "server_name", None) or getattr(s, "name", None) or str(s))
if i < 0 or i >= len(names):
await callback.answer("Сервер не найден", show_alert=True)
return
server_name = names[i]
key = f"grp_sel:{cluster_name}"
data = await state.get_data()
selected = set(data.get(key, []))
if server_name in selected:
selected.remove(server_name)
else:
selected.add(server_name)
await state.update_data({key: list(selected)})
await callback.message.edit_text(
f"<b>🗂 Выберите серверы в кластере <code>{cluster_name}</code> для назначения тарифной группы:</b>",
reply_markup=build_select_group_servers_kb(cluster_name, cluster_servers, selected),
)
@router.callback_query(AdminClusterCallback.filter(F.action == "reset_group_selection"))
async def reset_group_selection(
callback: CallbackQuery, callback_data: AdminClusterCallback, session: AsyncSession, state: FSMContext
):
cluster_name = callback_data.data
servers = await get_servers(session=session, include_enabled=True)
cluster_servers = servers.get(cluster_name, [])
await state.update_data({f"grp_sel:{cluster_name}": []})
await callback.message.edit_text(
f"<b>🗂 Выберите серверы в кластере <code>{cluster_name}</code> для назначения тарифной группы:</b>",
reply_markup=build_select_group_servers_kb(cluster_name, cluster_servers, set()),
)
@router.callback_query(AdminClusterCallback.filter(F.action == "choose_group"))
async def choose_group(
callback: CallbackQuery, callback_data: AdminClusterCallback, session: AsyncSession, state: FSMContext
):
cluster_name = callback_data.data
key = f"grp_sel:{cluster_name}"
data = await state.get_data()
selected = set(data.get(key, []))
if not selected:
await callback.answer("Сначала выберите хотя бы один сервер", show_alert=True)
return
groups = [(i, code) for i, code in enumerate(ALLOWED_GROUP_CODES)]
await callback.message.edit_text(
f"<b>📚 Выберите группу для {len(selected)} сервер(а/ов) кластера <code>{cluster_name}</code>:</b>",
reply_markup=build_tariff_group_selection_for_servers_kb(cluster_name, groups),
)
@router.callback_query(AdminClusterCallback.filter(F.action == "apply_group_to_servers"))
async def apply_group_to_servers(
callback: CallbackQuery, callback_data: AdminClusterCallback, session: AsyncSession, state: FSMContext
):
try:
cluster_name, idx_str = callback_data.data.split("|", 1)
i = int(idx_str)
groups = ALLOWED_GROUP_CODES
if i < 0 or i >= len(groups):
await callback.answer("Группа не найдена", show_alert=True)
return
group_code = groups[i]
key = f"grp_sel:{cluster_name}"
data = await state.get_data()
selected = set(data.get(key, []))
if not selected:
await callback.message.edit_text("❌ Не выбраны серверы для назначения группы.")
return
rows = await session.execute(select(Server.id, Server.server_name).where(Server.server_name.in_(selected)))
id_by_name = {name: sid for sid, name in rows.fetchall()}
server_ids = [id_by_name[n] for n in selected if n in id_by_name]
if not server_ids:
await callback.answer("Серверы не найдены", show_alert=True)
return
exist_rows = await session.execute(
select(ServerSpecialgroup.server_id).where(
and_(ServerSpecialgroup.server_id.in_(server_ids), ServerSpecialgroup.group_code == group_code)
)
)
already = {r[0] for r in exist_rows.fetchall()}
to_insert = [sid for sid in server_ids if sid not in already]
if to_insert:
session.add_all([ServerSpecialgroup(server_id=sid, group_code=group_code) for sid in to_insert])
await session.commit()
await state.update_data({key: []})
applied = ", ".join(sorted(selected))
await callback.message.edit_text(
f"✅ Группа <b>{group_code}</b> назначена серверам:\n<blockquote>{applied}</blockquote>",
reply_markup=build_cluster_management_kb(cluster_name),
)
except Exception as e:
logger.error(f"Ошибка при назначении группы тарифов: {e}")
await callback.message.edit_text("❌ Произошла ошибка при назначении группы.")
@router.callback_query(AdminClusterCallback.filter(F.action == "reset_cluster_groups"))
async def reset_cluster_groups(callback: CallbackQuery, callback_data: AdminClusterCallback, session: AsyncSession):
try:
cluster_name = callback_data.data
res = await session.execute(select(Server.id).where(Server.cluster_name == cluster_name))
server_ids = [row[0] for row in res.fetchall()]
if not server_ids:
await callback.answer("В кластере нет серверов", show_alert=True)
return
await session.execute(delete(ServerSpecialgroup).where(ServerSpecialgroup.server_id.in_(server_ids)))
await session.commit()
servers = await get_servers(session=session, include_enabled=True)
cluster_servers = servers.get(cluster_name, [])
await callback.message.edit_text(
f"✅ Все привязки групп сброшены для кластера <b>{cluster_name}</b>.",
reply_markup=build_manage_cluster_kb(cluster_servers, cluster_name),
)
except Exception as e:
logger.error(f"Ошибка при сбросе групп для кластера {cluster_name}: {e}")
await callback.message.edit_text("❌ Не удалось сбросить привязки групп.")
+95 -11
View File
@@ -57,21 +57,12 @@ def build_manage_cluster_kb(cluster_servers: list, cluster_name: str) -> InlineK
callback_data=AdminServerCallback(action="add", data=cluster_name).pack(),
)
)
builder.row(
InlineKeyboardButton(
text="🗂 Выбрать подгруппу тарифов",
callback_data=AdminClusterCallback(action="set_subgroup", data=cluster_name).pack(),
text="💸 Тариф(Установить/изменить)",
callback_data=AdminClusterCallback(action="attach_tariff_menu", data=cluster_name).pack(),
)
)
builder.row(
InlineKeyboardButton(
text="🧹 Сбросить все подгруппы",
callback_data=AdminClusterCallback(action="reset_cluster_subgroups", data=cluster_name).pack(),
)
)
builder.row(
InlineKeyboardButton(
text="🔙 Назад",
@@ -82,6 +73,37 @@ def build_manage_cluster_kb(cluster_servers: list, cluster_name: str) -> InlineK
return builder.as_markup()
def build_attach_tariff_kb(cluster_name: str) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(
text="🗂 Спецгруппы",
callback_data=AdminClusterCallback(action="set_group", data=cluster_name).pack(),
),
InlineKeyboardButton(
text="🧹 Сбросить",
callback_data=AdminClusterCallback(action="reset_cluster_groups", data=cluster_name).pack(),
),
)
builder.row(
InlineKeyboardButton(
text="🗂 Подгруппы",
callback_data=AdminClusterCallback(action="set_subgroup", data=cluster_name).pack(),
),
InlineKeyboardButton(
text="🧹 Сбросить",
callback_data=AdminClusterCallback(action="reset_cluster_subgroups", data=cluster_name).pack(),
),
)
builder.row(
InlineKeyboardButton(
text="🔙 Назад",
callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack(),
)
)
return builder.as_markup()
def build_select_subgroup_servers_kb(
cluster_name: str, cluster_servers: list, selected: set[str]
) -> InlineKeyboardMarkup:
@@ -242,3 +264,65 @@ def build_tariff_group_selection_kb(cluster_name: str, groups: list[tuple[int, s
)
builder.adjust(2, 1)
return builder.as_markup()
def build_select_group_servers_kb(cluster_name: str, cluster_servers: list, selected: set[str]) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
names = []
for s in cluster_servers:
if isinstance(s, str):
names.append(s)
elif isinstance(s, dict):
names.append(s.get("server_name") or s.get("name") or str(s))
else:
names.append(getattr(s, "server_name", None) or getattr(s, "name", None) or str(s))
for i, name in enumerate(names):
mark = "" if name in selected else "⬜️"
builder.row(
InlineKeyboardButton(
text=f"{mark} {name}",
callback_data=AdminClusterCallback(action="toggle_server_group", data=f"{cluster_name}|{i}").pack(),
)
)
builder.row(
InlineKeyboardButton(
text="📚 Выбрать спецгруппу",
callback_data=AdminClusterCallback(action="choose_group", data=cluster_name).pack(),
)
)
builder.row(
InlineKeyboardButton(
text="♻️ Сбросить выбор",
callback_data=AdminClusterCallback(action="reset_group_selection", data=cluster_name).pack(),
)
)
builder.row(
InlineKeyboardButton(
text="🔙 Назад",
callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack(),
)
)
return builder.as_markup()
def build_tariff_group_selection_for_servers_kb(
cluster_name: str, groups: list[tuple[int, str]]
) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
for group_id, group_code in groups:
builder.button(
text=group_code,
callback_data=AdminClusterCallback(
action="apply_group_to_servers", data=f"{cluster_name}|{group_id}"
).pack(),
)
builder.row(
InlineKeyboardButton(
text="⬅️ Назад",
callback_data=AdminClusterCallback(action="set_group", data=cluster_name).pack(),
)
)
builder.adjust(2, 1)
return builder.as_markup()
+3 -1
View File
@@ -56,11 +56,13 @@ async def handle_modules(callback_query: CallbackQuery, state: FSMContext, sessi
total_pages = max(1, (len(items) + per_page - 1) // per_page)
page = min(page, total_pages)
start = (page - 1) * per_page
chunk = items[start:start + per_page]
chunk = items[start : start + per_page]
if chunk:
def fmt(n, v):
return f"{n} v{v}" if v else n
lines = "\n".join(f"{fmt(n, v)}" for n, v in chunk)
text = f"🧩 Мои модули\n\nНайдено: {len(items)}\n<blockquote>{lines}</blockquote>"
else:
+7 -9
View File
@@ -45,24 +45,24 @@ async def send_broadcast_batch(bot, messages, batch_size=15, session=None):
try:
if photo:
result = await bot.send_photo(
await bot.send_photo(
chat_id=tg_id, photo=photo, caption=text, parse_mode="HTML", reply_markup=keyboard
)
else:
result = await bot.send_message(chat_id=tg_id, text=text, parse_mode="HTML", reply_markup=keyboard)
await bot.send_message(chat_id=tg_id, text=text, parse_mode="HTML", reply_markup=keyboard)
results.append(True)
except TelegramRetryAfter as e:
retry_in = int(e.retry_after) + 1
logger.warning(f"⚠️ Flood control: повтор через {retry_in} сек. для пользователя {tg_id}")
await asyncio.sleep(e.retry_after)
try:
if photo:
result = await bot.send_photo(
await bot.send_photo(
chat_id=tg_id, photo=photo, caption=text, parse_mode="HTML", reply_markup=keyboard
)
else:
result = await bot.send_message(chat_id=tg_id, text=text, parse_mode="HTML", reply_markup=keyboard)
await bot.send_message(chat_id=tg_id, text=text, parse_mode="HTML", reply_markup=keyboard)
results.append(True)
except TelegramForbiddenError:
logger.warning(f"🚫 Бот заблокирован пользователем {tg_id}.")
@@ -79,7 +79,7 @@ async def send_broadcast_batch(bot, messages, batch_size=15, session=None):
logger.error(f"❌ Ошибка повторной отправки пользователю {tg_id}: {retry_error}")
await try_add_blocked_user(tg_id, session)
results.append(False)
except TelegramForbiddenError:
logger.warning(f"🚫 Бот заблокирован пользователем {tg_id}.")
await try_add_blocked_user(tg_id, session)
@@ -147,9 +147,7 @@ async def get_recipients(session: AsyncSession, send_to: str, cluster_name: str
.where(~User.tg_id.in_(banned_tg_ids))
)
elif send_to == "hotleads":
subquery_active_keys = (
select(Key.tg_id).where(Key.expiry_time > now_ms).distinct()
)
subquery_active_keys = select(Key.tg_id).where(Key.expiry_time > now_ms).distinct()
query = (
select(distinct(User.tg_id))
.join(Payment, User.tg_id == Payment.tg_id)
+1 -1
View File
@@ -458,7 +458,7 @@ async def handle_balance_add(
if amount is not None:
amount = int(amount)
old_balance = await get_balance(session, tg_id)
if amount >= 0:
await update_balance(session, tg_id, amount)
new_balance = old_balance + amount
+1 -1
View File
@@ -13,7 +13,7 @@ from sqlalchemy import or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from bot import bot
from config import DISCOUNT_ACTIVE_HOURS, USE_NEW_PAYMENT_FLOW, RENEW_BUTTON_BEFORE_DAYS
from config import DISCOUNT_ACTIVE_HOURS, RENEW_BUTTON_BEFORE_DAYS, USE_NEW_PAYMENT_FLOW
from database import (
check_tariff_exists,
get_balance,
+21 -1
View File
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from config import HAPP_CRYPTOLINK, PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
from database import get_servers, get_tariff_by_id, store_key
from database.models import User
from handlers.utils import check_server_key_limit
from handlers.utils import ALLOWED_GROUP_CODES, check_server_key_limit
from logger import logger
from panels._3xui import (
ClientConfig,
@@ -73,6 +73,26 @@ async def create_key_on_cluster(
subgroup_servers = [s for s in enabled_servers if subgroup_title in s.get("tariff_subgroups", [])]
if subgroup_servers:
enabled_servers = subgroup_servers
else:
logger.warning(
f"[Key Creation] В кластере {cluster_id} не найдено серверов для подгруппы '{subgroup_title}'. Использую весь кластер."
)
special = None
if is_trial:
special = "trial"
elif tariff:
gc = (tariff.get("group_code") or "").lower()
if gc in ALLOWED_GROUP_CODES:
special = gc
if special:
bound_servers = [s for s in enabled_servers if special in (s.get("special_groups") or [])]
if bound_servers:
enabled_servers = bound_servers
else:
logger.warning(
f"[Key Creation] В кластере {cluster_id} нет серверов со спецгруппой '{special}'. Использую весь кластер."
)
remnawave_servers = [
s
+18 -12
View File
@@ -9,16 +9,16 @@ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from config import (
NOTIFICATION_TIME,
NOTIFY_10H_ENABLED,
NOTIFY_10H_HOURS,
NOTIFY_24H_ENABLED,
NOTIFY_24H_HOURS,
NOTIFY_DELETE_DELAY,
NOTIFY_DELETE_KEY,
NOTIFY_HOT_LEADS,
NOTIFY_INACTIVE_TRAFFIC,
NOTIFY_RENEW,
NOTIFY_RENEW_EXPIRED,
NOTIFY_24H_ENABLED,
NOTIFY_24H_HOURS,
NOTIFY_10H_ENABLED,
NOTIFY_10H_HOURS,
TRIAL_TIME_DISABLE,
)
from database import (
@@ -94,14 +94,18 @@ async def periodic_notifications(bot: Bot, *, sessionmaker: async_sessionmaker):
if NOTIFY_24H_ENABLED:
try:
threshold_24h = int((datetime.now(moscow_tz) + timedelta(hours=NOTIFY_24H_HOURS)).timestamp() * 1000)
threshold_24h = int(
(datetime.now(moscow_tz) + timedelta(hours=NOTIFY_24H_HOURS)).timestamp() * 1000
)
await notify_24h_keys(bot, session, current_time, threshold_24h, keys)
except Exception as e:
logger.error(f"Ошибка в notify_24h_keys: {e}")
if NOTIFY_10H_ENABLED:
try:
threshold_10h = int((datetime.now(moscow_tz) + timedelta(hours=NOTIFY_10H_HOURS)).timestamp() * 1000)
threshold_10h = int(
(datetime.now(moscow_tz) + timedelta(hours=NOTIFY_10H_HOURS)).timestamp() * 1000
)
await notify_10h_keys(bot, session, current_time, threshold_10h, keys)
except Exception as e:
logger.error(f"Ошибка в notify_10h_keys: {e}")
@@ -165,7 +169,7 @@ async def notify_24h_keys(
continue
expiry_data = await prepare_key_expiry_data(key, session, current_time)
notification_text = KEY_EXPIRY.format(
email=email,
hours_left_formatted=expiry_data["hours_left_formatted"],
@@ -249,7 +253,7 @@ async def notify_10h_keys(
continue
expiry_data = await prepare_key_expiry_data(key, session, current_time)
notification_text = KEY_EXPIRY.format(
email=email,
hours_left_formatted=expiry_data["hours_left_formatted"],
@@ -486,15 +490,17 @@ async def process_auto_renew_or_notify(
if not selected_tariff:
expiry_data = await prepare_key_expiry_data(key, conn, int(datetime.now(moscow_tz).timestamp() * 1000))
use_change_tariff_kb = False
if tariff_id and await check_tariff_exists(conn, tariff_id):
current_tariff = await get_tariff_by_id(conn, tariff_id)
if current_tariff:
forbidden_groups = ["discounts", "discounts_max", "gifts", "trial"]
try:
hook_results = await run_hooks("renewal_forbidden_groups", chat_id=tg_id, admin=False, session=conn)
hook_results = await run_hooks(
"renewal_forbidden_groups", chat_id=tg_id, admin=False, session=conn
)
for hook_result in hook_results:
additional_groups = hook_result.get("additional_groups", [])
forbidden_groups.extend(additional_groups)
@@ -526,7 +532,7 @@ async def process_auto_renew_or_notify(
keyboard = build_change_tariff_kb(email)
else:
keyboard = build_notification_kb(email)
await add_notification(conn, tg_id, notification_id)
await send_notification(bot, tg_id, standard_photo, message_text, keyboard)
return
+3 -2
View File
@@ -1,5 +1,6 @@
import asyncio
import os
from datetime import datetime
import aiofiles
@@ -176,7 +177,7 @@ async def prepare_key_expiry_data(key, session: AsyncSession, current_time: int)
tariff_name = ""
tariff_details = ""
if getattr(key, "tariff_id", None):
tariff = await get_tariff_by_id(session, key.tariff_id)
if tariff:
@@ -192,7 +193,7 @@ async def prepare_key_expiry_data(key, session: AsyncSession, current_time: int)
lines.append(f"Трафик: {traffic_text}")
lines.append(f"Устройств: {devices_text}")
tariff_details = "\n" + "\n".join(lines)
return {
"hours_left_formatted": hours_left_formatted,
"formatted_expiry_date": formatted_expiry_date,
+3
View File
@@ -25,6 +25,9 @@ from hooks.hooks import run_hooks
from logger import logger
ALLOWED_GROUP_CODES = ["trial", "discounts", "discounts_max"]
async def generate_random_email(
length: int = 8,
session: AsyncSession | None = None,
+2 -2
View File
@@ -37,7 +37,7 @@ class SubscriptionMiddleware(BaseMiddleware):
return await handler(event, data)
if event.message.from_user.is_bot:
return await handler(event, data)
tg_id = event.message.from_user.id
message = event.message
from_user = event.message.from_user
@@ -48,7 +48,7 @@ class SubscriptionMiddleware(BaseMiddleware):
return await handler(event, data)
if event.callback_query.from_user.is_bot:
return await handler(event, data)
tg_id = event.callback_query.from_user.id
message = event.callback_query.message
from_user = event.callback_query.from_user