generated from FOSS/Bot-Template
feat(admin): add admin panel and reminder system
This commit is contained in:
+129
-29
@@ -8,10 +8,19 @@ from datetime import datetime
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import (InlineKeyboardButton, KeyboardButton, WebAppInfo, Message, InlineKeyboardMarkup,
|
||||
ReplyKeyboardMarkup)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from aiogram.types import (
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
WebAppInfo,
|
||||
Message,
|
||||
KeyboardButton,
|
||||
ReplyKeyboardMarkup,
|
||||
CallbackQuery,
|
||||
)
|
||||
from pytz import timezone
|
||||
|
||||
from tgbot.database.db_settings import SettingsRepository
|
||||
from tgbot.data.config import get_admins, BOT_TIMEZONE
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
@@ -24,11 +33,11 @@ def rkb(text: str) -> KeyboardButton:
|
||||
|
||||
# Быстрая сборка инлайн-кнопки
|
||||
def ikb(
|
||||
text: str,
|
||||
data: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
switch: Optional[str] = None,
|
||||
web: Optional[str] = None,
|
||||
text: str,
|
||||
data: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
switch: Optional[str] = None,
|
||||
web: Optional[str] = None,
|
||||
) -> InlineKeyboardButton:
|
||||
if data is not None:
|
||||
return InlineKeyboardButton(text=text, callback_data=data)
|
||||
@@ -52,11 +61,11 @@ async def del_message(message: Message):
|
||||
|
||||
# Отправка текста с фото, если оно передано или обычным сообщением
|
||||
async def smart_message(
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
text: str,
|
||||
keyboard: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
|
||||
photo: Optional[str] = None,
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
text: str,
|
||||
keyboard: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
|
||||
photo: Optional[str] = None,
|
||||
):
|
||||
if photo is not None and photo.title() != "None":
|
||||
await bot.send_photo(
|
||||
@@ -85,7 +94,9 @@ async def send_admins(bot: Bot, text: str, markup=None, not_me=0):
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось отправить сообщение админу %s", admin, exc_info=True)
|
||||
bot_logger.warning(
|
||||
"Не удалось отправить сообщение админу %s", admin, exc_info=True
|
||||
)
|
||||
|
||||
|
||||
################################## РАЗНОЕ ######################################
|
||||
@@ -108,7 +119,7 @@ def clear_list(get_list: list) -> list:
|
||||
|
||||
# Делит список на части нужного размера
|
||||
def split_list(get_list: list, count: int) -> List[list]:
|
||||
return [get_list[i:i + count] for i in range(0, len(get_list), count)]
|
||||
return [get_list[i : i + count] for i in range(0, len(get_list), count)]
|
||||
|
||||
|
||||
# Возвращает текущую дату, при full=True еще и время
|
||||
@@ -137,11 +148,17 @@ def convert_date(from_time, full=True, second=True) -> Union[str, int]:
|
||||
if from_time.isdigit():
|
||||
from_timestamp = int(from_time)
|
||||
if full:
|
||||
to_time = datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y %H:%M:%S")
|
||||
to_time = datetime.fromtimestamp(from_timestamp, bot_timezone).strftime(
|
||||
"%d.%m.%Y %H:%M:%S"
|
||||
)
|
||||
elif second:
|
||||
to_time = datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y %H:%M")
|
||||
to_time = datetime.fromtimestamp(from_timestamp, bot_timezone).strftime(
|
||||
"%d.%m.%Y %H:%M"
|
||||
)
|
||||
else:
|
||||
to_time = datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y")
|
||||
to_time = datetime.fromtimestamp(from_timestamp, bot_timezone).strftime(
|
||||
"%d.%m.%Y"
|
||||
)
|
||||
else:
|
||||
parts = from_time.split()
|
||||
|
||||
@@ -215,20 +232,21 @@ def gen_password(len_password: int = 16, type_password: str = "default") -> str:
|
||||
# Склоняет единицы времени под число
|
||||
def convert_times(get_time: int, get_type: str = "day") -> str:
|
||||
get_time = int(get_time)
|
||||
if get_time < 0: get_time = 0
|
||||
if get_time < 0:
|
||||
get_time = 0
|
||||
|
||||
if get_type == "second":
|
||||
get_list = ['секунда', 'секунды', 'секунд']
|
||||
get_list = ["секунда", "секунды", "секунд"]
|
||||
elif get_type == "minute":
|
||||
get_list = ['минута', 'минуты', 'минут']
|
||||
get_list = ["минута", "минуты", "минут"]
|
||||
elif get_type == "hour":
|
||||
get_list = ['час', 'часа', 'часов']
|
||||
get_list = ["час", "часа", "часов"]
|
||||
elif get_type == "day":
|
||||
get_list = ['день', 'дня', 'дней']
|
||||
get_list = ["день", "дня", "дней"]
|
||||
elif get_type == "month":
|
||||
get_list = ['месяц', 'месяца', 'месяцев']
|
||||
get_list = ["месяц", "месяца", "месяцев"]
|
||||
else:
|
||||
get_list = ['год', 'года', 'лет']
|
||||
get_list = ["год", "года", "лет"]
|
||||
|
||||
if get_time % 10 == 1 and get_time % 100 != 11:
|
||||
count = 0
|
||||
@@ -244,9 +262,9 @@ def convert_times(get_time: int, get_type: str = "day") -> str:
|
||||
def is_bool(value: Union[bool, str, int]) -> bool:
|
||||
value = str(value).strip().lower()
|
||||
|
||||
if value in ('y', 'yes', 't', 'true', 'on', '1'):
|
||||
if value in ("y", "yes", "t", "true", "on", "1"):
|
||||
return True
|
||||
elif value in ('n', 'no', 'f', 'false', 'off', '0'):
|
||||
elif value in ("n", "no", "f", "false", "off", "0"):
|
||||
return False
|
||||
else:
|
||||
raise ValueError(f"Некорректное bool-значение: {value}")
|
||||
@@ -266,9 +284,11 @@ def snum(amount: Union[int, float], remains: int = 2) -> str:
|
||||
str_amount = str_amount[:remains_save]
|
||||
|
||||
if "." in str(str_amount):
|
||||
while str(str_amount).endswith('0'): str_amount = str(str_amount)[:-1]
|
||||
while str(str_amount).endswith("0"):
|
||||
str_amount = str(str_amount)[:-1]
|
||||
|
||||
if str(str_amount).endswith('.'): str_amount = str(str_amount)[:-1]
|
||||
if str(str_amount).endswith("."):
|
||||
str_amount = str(str_amount)[:-1]
|
||||
|
||||
return str(str_amount)
|
||||
|
||||
@@ -299,7 +319,8 @@ def is_number(get_number: Union[str, int, float]) -> bool:
|
||||
if str(get_number).isdigit():
|
||||
return True
|
||||
else:
|
||||
if "," in str(get_number): get_number = str(get_number).replace(",", ".")
|
||||
if "," in str(get_number):
|
||||
get_number = str(get_number).replace(",", ".")
|
||||
|
||||
try:
|
||||
float(get_number)
|
||||
@@ -318,3 +339,82 @@ def format_rate(amount: Union[float, int], around: int = 2) -> str:
|
||||
response = response.rstrip("0").rstrip(".")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# ─── Вспомогательные функции ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _interval_label(hours: int) -> str:
|
||||
labels = {0: "выкл", 5: "5 ч", 12: "12 ч", 24: "24 ч"}
|
||||
return labels.get(hours, f"{hours} ч")
|
||||
|
||||
|
||||
def _status_icon(flag: bool) -> str:
|
||||
return "✅" if flag else "❌"
|
||||
|
||||
|
||||
def _panel_text(s) -> str:
|
||||
remind = _interval_label(s.remind_interval)
|
||||
status = _status_icon(s.status_work)
|
||||
|
||||
msg_info = s.misc_message or "не задано"
|
||||
btn_info = (
|
||||
f"{s.misc_finl_text} → {s.misc_finl_link}"
|
||||
if s.misc_finl_text and s.misc_finl_link
|
||||
else "нет"
|
||||
)
|
||||
|
||||
return (
|
||||
"⚙️ <b>Панель управления</b>\n\n"
|
||||
f"<b>🤖 Статус бота</b>: {status}\n"
|
||||
f"<b>🔔 Напоминания</b>: {remind}\n"
|
||||
f"<b>📝 Сообщение</b>: <blockquote>{msg_info}\n</blockquote>\n"
|
||||
f"<b>🔗 Кнопка</b>: <i>{btn_info}</i>"
|
||||
)
|
||||
|
||||
|
||||
def _panel_kb(s) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
status_label = "🔴 Выключить бота" if s.status_work else "🟢 Включить бота"
|
||||
kb.row(ikb(status_label, data="adm:toggle"))
|
||||
kb.row(ikb("✏️ Изменить сообщение", data="adm:set_msg"))
|
||||
kb.row(ikb("🔔 Интервал напоминаний", data="adm:interval"))
|
||||
if s.misc_finl_text and s.misc_finl_link:
|
||||
kb.row(
|
||||
ikb("🔗 Изменить кнопку", data="adm:set_btn"),
|
||||
ikb("🗑 Убрать кнопку", data="adm:clr_btn"),
|
||||
)
|
||||
else:
|
||||
kb.row(ikb("🔗 Добавить кнопку", data="adm:set_btn"))
|
||||
kb.row(ikb("✖️ Закрыть", data="close_this"))
|
||||
return kb.as_markup()
|
||||
|
||||
|
||||
def _interval_kb(current: int) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
for h in (5, 12, 24):
|
||||
mark = "✔ " if current == h else ""
|
||||
kb.add(ikb(f"{mark}{_interval_label(h)}", data=f"adm:int:{h}"))
|
||||
mark = "✔ " if current == 0 else ""
|
||||
kb.add(ikb(f"{mark}Выкл", data="adm:int:0"))
|
||||
kb.adjust(3, 1)
|
||||
kb.row(ikb("◀ Назад", data="adm:main"))
|
||||
return kb.as_markup()
|
||||
|
||||
|
||||
async def _show_panel(target, settings=None) -> None:
|
||||
"""Отправить или обновить панель (target — Message или CallbackQuery)."""
|
||||
if settings is None:
|
||||
settings = await SettingsRepository().get()
|
||||
|
||||
text = _panel_text(settings)
|
||||
kb = _panel_kb(settings)
|
||||
|
||||
if isinstance(target, CallbackQuery):
|
||||
try:
|
||||
await target.message.edit_text(text, reply_markup=kb)
|
||||
except Exception:
|
||||
await target.message.answer(text, reply_markup=kb)
|
||||
await target.answer()
|
||||
else:
|
||||
await target.answer(text, reply_markup=kb)
|
||||
|
||||
Reference in New Issue
Block a user