Initial local state
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import html
|
||||
import secrets
|
||||
import string
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import (
|
||||
CopyTextButton,
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
KeyboardButton,
|
||||
Message,
|
||||
ReplyKeyboardMarkup,
|
||||
WebAppInfo,
|
||||
)
|
||||
from pytz import timezone
|
||||
|
||||
from tgbot.data.config import BOT_TIMEZONE, get_admins
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
|
||||
# Быстрая сборка reply-кнопки
|
||||
def rkb(text: str) -> KeyboardButton:
|
||||
return KeyboardButton(text=text)
|
||||
|
||||
|
||||
# Быстрая сборка inline-кнопки
|
||||
def ikb(
|
||||
text: str,
|
||||
data: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
switch: Optional[str] = None,
|
||||
web: Optional[str] = None,
|
||||
copy: Optional[str] = None,
|
||||
login: Optional[str] = None,
|
||||
) -> InlineKeyboardButton:
|
||||
if data is not None:
|
||||
return InlineKeyboardButton(text=text, callback_data=data)
|
||||
if url is not None:
|
||||
return InlineKeyboardButton(text=text, url=url)
|
||||
if switch is not None:
|
||||
return InlineKeyboardButton(text=text, switch_inline_query=switch)
|
||||
if web is not None:
|
||||
return InlineKeyboardButton(text=text, web_app=WebAppInfo(url=web))
|
||||
if copy is not None:
|
||||
return InlineKeyboardButton(text=text, copy_text=CopyTextButton(text=copy))
|
||||
if login is not None:
|
||||
return InlineKeyboardButton(text=text, url=f"https://t.me/{login}")
|
||||
|
||||
raise ValueError("Не указано действие для inline-кнопки")
|
||||
|
||||
|
||||
# Удаление сообщения с логированием ошибок
|
||||
async def del_message(message: Message) -> None:
|
||||
try:
|
||||
await message.delete()
|
||||
except Exception:
|
||||
bot_logger.debug("Не удалось удалить сообщение", exc_info=True)
|
||||
|
||||
|
||||
# Отправка фото или обычного сообщения
|
||||
async def smart_message(
|
||||
bot: Bot,
|
||||
user_id: int,
|
||||
text: str,
|
||||
keyboard: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None,
|
||||
photo: Optional[str] = None,
|
||||
) -> None:
|
||||
if photo is not None and photo.title() != "None":
|
||||
await bot.send_photo(
|
||||
chat_id=user_id,
|
||||
photo=photo,
|
||||
caption=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
|
||||
|
||||
# Отправка сообщения всем админам
|
||||
async def send_admins(
|
||||
bot: Bot,
|
||||
text: str,
|
||||
keyboard: Optional[InlineKeyboardMarkup] = None,
|
||||
markup: Optional[InlineKeyboardMarkup] = None,
|
||||
not_me: int = 0,
|
||||
) -> None:
|
||||
reply_markup = markup or keyboard
|
||||
|
||||
for admin in get_admins():
|
||||
try:
|
||||
if str(admin) != str(not_me):
|
||||
await bot.send_message(
|
||||
admin,
|
||||
text,
|
||||
reply_markup=reply_markup,
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось отправить сообщение админу %s", admin, exc_info=True)
|
||||
|
||||
|
||||
# Логирование ошибки и отправка админам
|
||||
async def send_errors(bot: Bot, error_code: int, error_text: str = "") -> None:
|
||||
text_error = f"myError {error_code}: {error_text}"
|
||||
|
||||
bot_logger.warning(text_error)
|
||||
await send_admins(bot, text_error)
|
||||
|
||||
|
||||
# Очистка лишних отступов в многострочном тексте
|
||||
def ded(get_text: str) -> str:
|
||||
if get_text is not None:
|
||||
split_text = get_text.split("\n")
|
||||
|
||||
if split_text[0] == "": split_text.pop(0)
|
||||
if split_text[-1] == "": split_text.pop()
|
||||
save_text = []
|
||||
|
||||
for text in split_text:
|
||||
while text.startswith(" "):
|
||||
text = text[1:]
|
||||
|
||||
save_text.append(text)
|
||||
get_text = "\n".join(save_text)
|
||||
else:
|
||||
get_text = ""
|
||||
|
||||
return get_text
|
||||
|
||||
|
||||
# Экранирование HTML-символов для телеграм разметки
|
||||
def clear_html(get_text: Optional[str]) -> str:
|
||||
return html.escape(get_text or "", quote=False)
|
||||
|
||||
|
||||
# Очистка пустых и мусорных элементов из списка
|
||||
def clear_list(get_list: list) -> list:
|
||||
trash = {"", " ", ".", ",", "\r", "\n"}
|
||||
|
||||
return [value for value in get_list if value not in trash]
|
||||
|
||||
|
||||
# Склейка нескольких списков в один
|
||||
def convert_list(get_lists: List[list]) -> list:
|
||||
save_lists = []
|
||||
|
||||
for select_list in get_lists:
|
||||
cache_list = clear_list(select_list)
|
||||
|
||||
if len(cache_list) > 0:
|
||||
save_lists += cache_list
|
||||
|
||||
return save_lists
|
||||
|
||||
|
||||
# Разделение списка на части нужного размера
|
||||
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)]
|
||||
|
||||
|
||||
# Старое имя для разделения сообщений
|
||||
def split_messages(get_list: list, count: int) -> List[list]:
|
||||
return split_list(get_list, count)
|
||||
|
||||
|
||||
# Получение текущей даты
|
||||
def get_date(full: bool = True) -> str:
|
||||
bot_timezone = timezone(BOT_TIMEZONE)
|
||||
|
||||
if full:
|
||||
return datetime.now(bot_timezone).strftime("%d.%m.%Y %H:%M:%S")
|
||||
|
||||
return datetime.now(bot_timezone).strftime("%d.%m.%Y")
|
||||
|
||||
|
||||
# Получение Unix-времени в секундах или наносекундах
|
||||
def get_unix(full: bool = False, nano: bool = False) -> int:
|
||||
if full or nano:
|
||||
return time.time_ns()
|
||||
|
||||
return int(time.time())
|
||||
|
||||
|
||||
# Конвертация даты в Unix и обратно
|
||||
def convert_date(from_time, full=True, second=True) -> Union[str, int]:
|
||||
bot_timezone = timezone(BOT_TIMEZONE)
|
||||
from_time = str(from_time).strip().replace("-", ".")
|
||||
|
||||
if from_time.isdigit():
|
||||
from_timestamp = int(from_time)
|
||||
|
||||
if full:
|
||||
return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y %H:%M:%S")
|
||||
if second:
|
||||
return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y %H:%M")
|
||||
|
||||
return datetime.fromtimestamp(from_timestamp, bot_timezone).strftime("%d.%m.%Y")
|
||||
|
||||
parts = from_time.split()
|
||||
|
||||
if len(parts) == 2 and ":" in parts[0]:
|
||||
time_part, date_part = parts
|
||||
elif len(parts) == 2:
|
||||
date_part, time_part = parts
|
||||
else:
|
||||
date_part, time_part = from_time, "00:00:00"
|
||||
|
||||
date_values = date_part.split(".")
|
||||
time_values = time_part.split(":")
|
||||
|
||||
if len(time_values) == 2:
|
||||
time_values.append("0")
|
||||
|
||||
if len(date_values[0]) == 4:
|
||||
x_year, x_month, x_day = date_values[0], date_values[1], date_values[2]
|
||||
else:
|
||||
x_day, x_month, x_year = date_values[0], date_values[1], date_values[2]
|
||||
|
||||
date_time = datetime(
|
||||
int(x_year),
|
||||
int(x_month),
|
||||
int(x_day),
|
||||
int(time_values[0]),
|
||||
int(time_values[1]),
|
||||
int(time_values[2]),
|
||||
)
|
||||
date_time = bot_timezone.localize(date_time)
|
||||
|
||||
return int(date_time.timestamp())
|
||||
|
||||
|
||||
# Генерация числового ID
|
||||
def gen_id(len_id: int = 16) -> int:
|
||||
if len_id <= 0:
|
||||
raise ValueError("Длина ID должна быть больше нуля")
|
||||
|
||||
first_digit = secrets.choice("123456789")
|
||||
other_digits = "".join(secrets.choice(string.digits) for _ in range(len_id - 1))
|
||||
|
||||
return int(f"{first_digit}{other_digits}")
|
||||
|
||||
|
||||
# Генерация пароля под разные сценарии
|
||||
def gen_password(len_password: int = 16, type_password: str = "default") -> str:
|
||||
if len_password <= 0:
|
||||
raise ValueError("Длина пароля должна быть больше нуля")
|
||||
|
||||
if type_password == "default":
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
elif type_password == "letter":
|
||||
alphabet = string.ascii_letters
|
||||
elif type_password == "number":
|
||||
alphabet = string.digits
|
||||
elif type_password == "onechar":
|
||||
alphabet = string.digits
|
||||
else:
|
||||
raise ValueError("Неизвестный тип пароля")
|
||||
|
||||
random_chars = "".join(secrets.choice(alphabet) for _ in range(len_password))
|
||||
|
||||
if type_password == "onechar":
|
||||
random_chars = f"{secrets.choice(string.ascii_letters)}{random_chars[1:]}"
|
||||
|
||||
return random_chars
|
||||
|
||||
|
||||
# Склонение единицы времени под число
|
||||
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_type == "second":
|
||||
values = ["секунда", "секунды", "секунд"]
|
||||
elif get_type == "minute":
|
||||
values = ["минута", "минуты", "минут"]
|
||||
elif get_type == "hour":
|
||||
values = ["час", "часа", "часов"]
|
||||
elif get_type == "day":
|
||||
values = ["день", "дня", "дней"]
|
||||
elif get_type == "month":
|
||||
values = ["месяц", "месяца", "месяцев"]
|
||||
else:
|
||||
values = ["год", "года", "лет"]
|
||||
|
||||
if get_time % 10 == 1 and get_time % 100 != 11:
|
||||
count = 0
|
||||
elif 2 <= get_time % 10 <= 4 and (get_time % 100 < 10 or get_time % 100 >= 20):
|
||||
count = 1
|
||||
else:
|
||||
count = 2
|
||||
|
||||
return f"{get_time} {values[count]}"
|
||||
|
||||
|
||||
# Старое имя для склонения дней
|
||||
def convert_day(day: int) -> str:
|
||||
return convert_times(day)
|
||||
|
||||
|
||||
# Приведение строки или числа к bool
|
||||
def is_bool(value: Union[bool, str, int]) -> bool:
|
||||
value = str(value).strip().lower()
|
||||
|
||||
if value in ("y", "yes", "t", "true", "on", "1"):
|
||||
return True
|
||||
if value in ("n", "no", "f", "false", "off", "0"):
|
||||
return False
|
||||
|
||||
raise ValueError(f"Некорректное bool-значение: {value}")
|
||||
|
||||
|
||||
# Форматирование числа без лишних нулей
|
||||
def snum(amount: Union[int, float], remains: int = 2) -> str:
|
||||
format_str = "{:." + str(remains) + "f}"
|
||||
str_amount = format_str.format(float(amount))
|
||||
|
||||
if remains != 0 and "." in str_amount:
|
||||
remains_find = str_amount.find(".")
|
||||
remains_save = remains_find + 8 - (8 - remains) + 1
|
||||
str_amount = str_amount[:remains_save]
|
||||
|
||||
if "." in str(str_amount):
|
||||
while str(str_amount).endswith("0"):
|
||||
str_amount = str(str_amount)[:-1]
|
||||
|
||||
if str(str_amount).endswith("."):
|
||||
str_amount = str(str_amount)[:-1]
|
||||
|
||||
return str(str_amount)
|
||||
|
||||
|
||||
# Приведение значения к int или float
|
||||
def to_float(get_number, remains: int = 2) -> Union[int, float]:
|
||||
value = str(get_number).strip().replace(" ", "").replace(",", ".")
|
||||
number = round(float(value), remains)
|
||||
|
||||
if number.is_integer():
|
||||
return int(number)
|
||||
|
||||
return number
|
||||
|
||||
|
||||
# Старое имя для приведения числа
|
||||
def to_number(get_number, remains: int = 2) -> Union[int, float]:
|
||||
return to_float(get_number, remains)
|
||||
|
||||
|
||||
# Округление числа до int
|
||||
def to_int(get_number: float) -> int:
|
||||
value = str(get_number).replace(",", ".")
|
||||
|
||||
return int(round(float(value)))
|
||||
|
||||
|
||||
# Проверка, является ли значение числом
|
||||
def is_number(get_number: Union[str, int, float]) -> bool:
|
||||
if str(get_number).isdigit():
|
||||
return True
|
||||
|
||||
value = str(get_number).replace(",", ".")
|
||||
|
||||
try:
|
||||
float(value)
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
# Форматирование числа с разделением тысяч
|
||||
def format_rate(amount: Union[float, int], around: int = 2) -> str:
|
||||
value = str(amount).strip().replace(" ", "").replace(",", ".")
|
||||
number = round(float(value), around)
|
||||
response = f"{number:,.{around}f}".replace(",", " ")
|
||||
|
||||
if "." in response:
|
||||
response = response.rstrip("0").rstrip(".")
|
||||
|
||||
return response
|
||||
@@ -0,0 +1,33 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram import Bot
|
||||
from aiogram.types import BotCommand, BotCommandScopeChat, BotCommandScopeDefault
|
||||
|
||||
from tgbot.data.config import get_admins
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
|
||||
# Команды для юзеров
|
||||
user_commands = [
|
||||
BotCommand(command='start', description='♻️ Перезапустить бота'),
|
||||
BotCommand(command='support', description='☎️ Поддержка'),
|
||||
BotCommand(command='faq', description='❔ FAQ'),
|
||||
]
|
||||
|
||||
# Команды для админов
|
||||
admin_commands = [
|
||||
BotCommand(command='start', description='♻️ Перезапустить бота'),
|
||||
BotCommand(command='support', description='☎️ Поддержка'),
|
||||
BotCommand(command='faq', description='❔ FAQ'),
|
||||
BotCommand(command='db', description='📦 Получить Базу Данных'),
|
||||
BotCommand(command='log', description='🖨 Получить логи'),
|
||||
]
|
||||
|
||||
|
||||
# Установка команд
|
||||
async def set_commands(bot: Bot):
|
||||
await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault())
|
||||
|
||||
for admin in get_admins():
|
||||
try:
|
||||
await bot.set_my_commands(admin_commands, scope=BotCommandScopeChat(chat_id=admin))
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось установить команды админу %s", admin, exc_info=True)
|
||||
@@ -0,0 +1,67 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from typing import Union
|
||||
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from tgbot.data.config import get_admins
|
||||
from tgbot.database import Settingsx
|
||||
|
||||
|
||||
class IsAdmin(BaseFilter):
|
||||
# Проверка, что пользователь админ
|
||||
async def __call__(self, event: Union[Message, CallbackQuery]) -> bool:
|
||||
user = getattr(event, "from_user", None)
|
||||
|
||||
return bool(user and user.id in get_admins())
|
||||
|
||||
|
||||
class IsPrivate(BaseFilter):
|
||||
# Проверка приватного чата
|
||||
async def __call__(self, event: Union[Message, CallbackQuery]) -> bool:
|
||||
chat = getattr(event, "chat", None)
|
||||
message = getattr(event, "message", None)
|
||||
|
||||
if chat is None and message is not None:
|
||||
chat = message.chat
|
||||
|
||||
if chat is None:
|
||||
return True
|
||||
|
||||
return chat.type == "private"
|
||||
|
||||
|
||||
class IsWork(BaseFilter):
|
||||
# Проверка режима тех. работ
|
||||
async def __call__(self, event: Union[Message, CallbackQuery]) -> bool:
|
||||
user = getattr(event, "from_user", None)
|
||||
settings = await Settingsx().get()
|
||||
|
||||
if settings.status_work == "False" or (user and user.id in get_admins()):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class IsRefill(BaseFilter):
|
||||
# Проверка доступности пополнений
|
||||
async def __call__(self, event: Union[Message, CallbackQuery]) -> bool:
|
||||
user = getattr(event, "from_user", None)
|
||||
settings = await Settingsx().get()
|
||||
|
||||
if settings.status_refill == "True" or (user and user.id in get_admins()):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class IsBuy(BaseFilter):
|
||||
# Проверка доступности покупок
|
||||
async def __call__(self, event: Union[Message, CallbackQuery]) -> bool:
|
||||
user = getattr(event, "from_user", None)
|
||||
settings = await Settingsx().get()
|
||||
|
||||
if settings.status_buy == "True" or (user and user.id in get_admins()):
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,52 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from pathlib import Path
|
||||
|
||||
import colorlog
|
||||
|
||||
from tgbot.data.config import PATH_LOGS
|
||||
|
||||
LOG_FILE_MAX_BYTES = 5 * 1024 * 1024
|
||||
LOG_FILE_BACKUP_COUNT = 5
|
||||
|
||||
log_path = Path(PATH_LOGS)
|
||||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
bot_logger = logging.getLogger("tgbot")
|
||||
bot_logger.setLevel(logging.INFO)
|
||||
bot_logger.propagate = False
|
||||
|
||||
if not bot_logger.handlers:
|
||||
file_formatter = logging.Formatter(
|
||||
"%(levelname)s | %(asctime)s | %(name)s | %(filename)s:%(lineno)d | %(message)s",
|
||||
datefmt="%d-%m-%Y %H:%M:%S",
|
||||
)
|
||||
console_formatter = colorlog.ColoredFormatter(
|
||||
"%(log_color)s%(levelname)s%(reset)s | %(blue)s%(asctime)s%(reset)s | "
|
||||
"%(purple)s%(filename)s:%(lineno)d%(reset)s | %(message)s",
|
||||
datefmt="%d-%m-%Y %H:%M:%S",
|
||||
log_colors={
|
||||
"DEBUG": "cyan",
|
||||
"INFO": "green",
|
||||
"WARNING": "yellow",
|
||||
"ERROR": "red",
|
||||
"CRITICAL": "bold_red",
|
||||
},
|
||||
)
|
||||
|
||||
file_handler = RotatingFileHandler(
|
||||
log_path,
|
||||
maxBytes=LOG_FILE_MAX_BYTES,
|
||||
backupCount=LOG_FILE_BACKUP_COUNT,
|
||||
encoding="utf-8",
|
||||
)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(console_formatter)
|
||||
console_handler.setLevel(logging.INFO)
|
||||
|
||||
bot_logger.addHandler(file_handler)
|
||||
bot_logger.addHandler(console_handler)
|
||||
@@ -0,0 +1,7 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
from tgbot.services.api_session import AsyncRequestSession
|
||||
|
||||
FSM = FSMContext
|
||||
ARS = AsyncRequestSession
|
||||
@@ -0,0 +1,196 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import FSInputFile, CallbackQuery, Message
|
||||
|
||||
from tgbot.data.config import BOT_DATABASE_EXPORT, BOT_STATUS_NOTIFICATION, BOT_VERSION, PATH_DATABASE, get_admins, \
|
||||
get_text_desc
|
||||
from tgbot.database import Userx, Settingsx
|
||||
from tgbot.utils.const_functions import get_unix, get_date, ded, send_admins
|
||||
from tgbot.utils.misc.bot_logging import bot_logger
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
from tgbot.utils.text_functions import get_statistics
|
||||
|
||||
|
||||
# Автоматическая очистка ежедневной статистики после 00:00:15
|
||||
async def update_profit_day(bot: Bot):
|
||||
await send_admins(bot, await get_statistics())
|
||||
|
||||
await Settingsx().update(misc_profit_day=get_unix())
|
||||
|
||||
|
||||
# Автоматическая очистка еженедельной статистики в понедельник 00:00:10
|
||||
async def update_profit_week():
|
||||
await Settingsx().update(misc_profit_week=get_unix())
|
||||
|
||||
|
||||
# Автоматическое обновление счётчика каждый месяц первого числа в 00:00:05
|
||||
async def update_profit_month():
|
||||
await Settingsx().update(misc_profit_month=get_unix())
|
||||
|
||||
|
||||
# Автонастройка UNIX времени в БД
|
||||
async def autosettings_unix():
|
||||
now_day = datetime.now().day
|
||||
now_week = datetime.now().weekday()
|
||||
now_month = datetime.now().month
|
||||
now_year = datetime.now().year
|
||||
|
||||
unix_day = int(datetime.strptime(f"{now_day}.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S").timestamp())
|
||||
unix_week = unix_day - (now_week * 86400)
|
||||
unix_month = int(datetime.strptime(f"1.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S").timestamp())
|
||||
|
||||
await Settingsx().update(
|
||||
misc_profit_day=unix_day,
|
||||
misc_profit_week=unix_week,
|
||||
misc_profit_month=unix_month,
|
||||
)
|
||||
|
||||
|
||||
# Проверка на перенесение БД из старого бота в нового или указание токена нового бота
|
||||
async def check_bot_username(bot: Bot):
|
||||
get_login = await Settingsx().get()
|
||||
get_bot = await bot.get_me()
|
||||
|
||||
if get_bot.username != get_login.misc_bot:
|
||||
await Settingsx().update(misc_bot=get_bot.username)
|
||||
|
||||
|
||||
# Уведомление и проверка обновления при запуске бота
|
||||
async def startup_notify(bot: Bot, arSession: ARS):
|
||||
if len(get_admins()) >= 1 and BOT_STATUS_NOTIFICATION:
|
||||
await send_admins(
|
||||
bot,
|
||||
ded(f"""
|
||||
<b>✅ Бот был успешно запущен</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{get_text_desc()}
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
<code>❗ Данное сообщение видят только администраторы бота.</code>
|
||||
"""),
|
||||
)
|
||||
|
||||
await check_update(bot, arSession)
|
||||
|
||||
|
||||
# Автобэкапы БД для админов
|
||||
async def autobackup_admin(bot: Bot):
|
||||
if not BOT_DATABASE_EXPORT:
|
||||
return
|
||||
|
||||
for admin in get_admins():
|
||||
try:
|
||||
await bot.send_document(
|
||||
admin,
|
||||
FSInputFile(PATH_DATABASE),
|
||||
caption=f"<b>📦 #BACKUP | <code>{get_date(full=False)}</code></b>",
|
||||
disable_notification=True,
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось отправить автобэкап админу %s", admin, exc_info=True)
|
||||
|
||||
|
||||
# Проверка наличия обновлений бота
|
||||
async def check_update(bot: Bot, arSession: ARS):
|
||||
session = await arSession.get_session()
|
||||
|
||||
try:
|
||||
response = await session.get(
|
||||
"https://djimbo.dev/autoshop_update.json",
|
||||
headers={"Accept-Encoding": "gzip, deflate"},
|
||||
)
|
||||
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
|
||||
if float(response_data['version']) > float(BOT_VERSION):
|
||||
await send_admins(
|
||||
bot,
|
||||
ded(f"""
|
||||
<b>❇️ Вышло обновление: <a href='{response_data['download']}'>Скачать</a></b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
{response_data['text']}
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
<code>❗ Данное сообщение видят только администраторы бота.</code>
|
||||
"""),
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось проверить обновления", exc_info=True)
|
||||
|
||||
|
||||
# Расссылка админам о критических ошибках и обновлениях
|
||||
async def check_mail(bot: Bot, arSession: ARS):
|
||||
session = await arSession.get_session()
|
||||
|
||||
try:
|
||||
response = await session.get(
|
||||
"https://djimbo.dev/autoshop_mail.json",
|
||||
headers={"Accept-Encoding": "gzip, deflate"},
|
||||
)
|
||||
response_data = json.loads((await response.read()).decode())
|
||||
|
||||
if response_data['status']:
|
||||
await send_admins(
|
||||
bot,
|
||||
ded(f"""
|
||||
{response_data['text']}
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
<code>❗ Данное сообщение видят только администраторы бота.</code>
|
||||
"""),
|
||||
)
|
||||
except Exception:
|
||||
bot_logger.warning("Не удалось проверить сервисные уведомления", exc_info=True)
|
||||
|
||||
|
||||
# Вставка кастомных тэгов юзера в текст
|
||||
async def insert_tags(user_id: Union[int, str], text: str) -> str:
|
||||
get_user = await Userx().get_required(user_id=user_id)
|
||||
|
||||
if "{user_id}" in text:
|
||||
text = text.replace("{user_id}", f"<b>{get_user.user_id}</b>")
|
||||
if "{username}" in text:
|
||||
text = text.replace("{username}", f"<b>{get_user.user_login}</b>")
|
||||
if "{firstname}" in text:
|
||||
text = text.replace("{firstname}", f"<b>{get_user.user_name}</b>")
|
||||
|
||||
return text
|
||||
|
||||
|
||||
# Отправка рассылки
|
||||
async def functions_mail_make(bot: Bot, message: Message, call: CallbackQuery):
|
||||
users_receive, users_block, users_count = 0, 0, 0
|
||||
|
||||
get_users = await Userx().get_all()
|
||||
get_time = get_unix()
|
||||
|
||||
for user in get_users:
|
||||
try:
|
||||
await bot.copy_message(
|
||||
chat_id=user.user_id,
|
||||
from_chat_id=message.from_user.id,
|
||||
message_id=message.message_id,
|
||||
)
|
||||
users_receive += 1
|
||||
except Exception:
|
||||
users_block += 1
|
||||
bot_logger.debug("Пользователь %s не получил рассылку", user.user_id, exc_info=True)
|
||||
|
||||
users_count += 1
|
||||
|
||||
if users_count % 10 == 0:
|
||||
await call.message.edit_text(f"<b>📢 Рассылка началась... ({users_count}/{len(get_users)})</b>")
|
||||
|
||||
await asyncio.sleep(0.07)
|
||||
|
||||
await call.message.edit_text(
|
||||
ded(f"""
|
||||
<b>📢 Рассылка была завершена за <code>{get_unix() - get_time}сек</code></b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
👤 Всего пользователей: <code>{len(get_users)}</code>
|
||||
✅ Пользователей получило сообщение: <code>{users_receive}</code>
|
||||
❌ Пользователей не получило сообщение: <code>{users_block}</code>
|
||||
""")
|
||||
)
|
||||
@@ -0,0 +1,143 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from collections import defaultdict
|
||||
from typing import DefaultDict, List, Optional, Tuple
|
||||
|
||||
from tgbot.database import Categoryx, Itemx, Settingsx, Positionx, ModelCategory, ModelPosition
|
||||
|
||||
SYMBOLS_LIMIT = 4_000 # Кол-во макс символов в сообщение
|
||||
|
||||
|
||||
# Наличие товаров
|
||||
async def get_items_available(remover: int) -> Tuple[str, int, int]:
|
||||
get_categories = await Categoryx().get_all()
|
||||
get_positions = await Positionx().get_all()
|
||||
|
||||
item_counts = await Positionx().item_counts()
|
||||
|
||||
positions_by_category: DefaultDict[int, List[Tuple[str, float, int]]] = defaultdict(list)
|
||||
for position in get_positions:
|
||||
in_stock = item_counts.get(position.position_id, 0)
|
||||
|
||||
if in_stock > 0:
|
||||
positions_by_category[position.category_id].append(
|
||||
(position.position_name, position.position_price, in_stock)
|
||||
)
|
||||
|
||||
category_sections: List[Tuple[str, List[Tuple[str, float, int]]]] = []
|
||||
for category in get_categories:
|
||||
category_positions = positions_by_category.get(category.category_id, [])
|
||||
|
||||
if len(category_positions) == 0:
|
||||
continue
|
||||
|
||||
category_sections.append((category.category_name, category_positions))
|
||||
|
||||
pages = parse_items_available(category_sections)
|
||||
max_page = len(pages)
|
||||
|
||||
if max_page == 0:
|
||||
return "", 0, 0
|
||||
|
||||
remover = max(0, min(remover, max_page - 1))
|
||||
|
||||
return pages[remover], max_page, remover
|
||||
|
||||
|
||||
# Форматирование товаров для вывода наличия
|
||||
def _format_position_line(
|
||||
position_name: str,
|
||||
position_price: float,
|
||||
in_stock: int,
|
||||
max_len: Optional[int] = None,
|
||||
) -> str:
|
||||
prefix = "• "
|
||||
suffix = f" - {position_price}₽ - {in_stock} шт"
|
||||
full_line = f"{prefix}{position_name}{suffix}"
|
||||
|
||||
if max_len is None or len(full_line) <= max_len:
|
||||
return full_line
|
||||
|
||||
max_name_len = max(1, max_len - len(prefix) - len(suffix) - 1)
|
||||
short_name = f"{position_name[:max_name_len]}..."
|
||||
|
||||
return f"{prefix}{short_name}{suffix}"
|
||||
|
||||
|
||||
# Перебор товаров в списке для вывода
|
||||
def parse_items_available(category_sections: List[Tuple[str, List[Tuple[str, float, int]]]]) -> List[str]:
|
||||
current_page = ""
|
||||
pages: List[str] = []
|
||||
|
||||
for category_name, positions in category_sections:
|
||||
header_category = f"<b>➖➖➖ {category_name} ➖➖➖</b>"
|
||||
|
||||
header_block = header_category if current_page == "" else f"\n\n{header_category}"
|
||||
candidate_with_header = current_page + header_block
|
||||
|
||||
if len(candidate_with_header) > SYMBOLS_LIMIT:
|
||||
if current_page != "":
|
||||
pages.append(current_page)
|
||||
current_page = header_category
|
||||
else:
|
||||
current_page = candidate_with_header
|
||||
|
||||
for position_name, position_price, in_stock in positions:
|
||||
line = _format_position_line(position_name, position_price, in_stock)
|
||||
candidate_with_line = current_page + f"\n{line}"
|
||||
|
||||
if len(candidate_with_line) > SYMBOLS_LIMIT:
|
||||
if current_page != "":
|
||||
pages.append(current_page)
|
||||
|
||||
current_page = header_category
|
||||
|
||||
remain_len = SYMBOLS_LIMIT - len(current_page) - 1
|
||||
line = _format_position_line(position_name, position_price, in_stock, max_len=remain_len)
|
||||
current_page += f"\n{line}"
|
||||
else:
|
||||
current_page = candidate_with_line
|
||||
|
||||
if current_page != "":
|
||||
pages.append(current_page)
|
||||
|
||||
return pages
|
||||
|
||||
|
||||
# Получение категорий с товарами
|
||||
async def get_categories_items() -> List[ModelCategory]:
|
||||
get_settings = await Settingsx().get()
|
||||
|
||||
get_categories = await Categoryx().get_all()
|
||||
|
||||
save_categories = []
|
||||
|
||||
if get_settings.misc_hide_category == "True":
|
||||
for category in get_categories:
|
||||
get_positions = await get_positions_items(category.category_id)
|
||||
|
||||
if len(get_positions) >= 1:
|
||||
save_categories.append(category)
|
||||
else:
|
||||
save_categories = get_categories
|
||||
|
||||
return save_categories
|
||||
|
||||
|
||||
# Получение позиций с товарами
|
||||
async def get_positions_items(category_id: int) -> List[ModelPosition]:
|
||||
get_settings = await Settingsx().get()
|
||||
|
||||
get_positions = await Positionx().gets(category_id=category_id)
|
||||
|
||||
save_positions = []
|
||||
|
||||
if get_settings.misc_hide_position == "True":
|
||||
for position in get_positions:
|
||||
get_items = await Itemx().gets(position_id=position.position_id)
|
||||
|
||||
if len(get_items) >= 1:
|
||||
save_positions.append(position)
|
||||
else:
|
||||
save_positions = get_positions
|
||||
|
||||
return save_positions
|
||||
@@ -0,0 +1,438 @@
|
||||
# - *- coding: utf- 8 - *-
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
import pytz
|
||||
from aiogram import Bot
|
||||
from aiogram.types import LinkPreviewOptions
|
||||
from aiogram.utils.markdown import hide_link
|
||||
|
||||
from tgbot.data.config import BOT_TIMEZONE
|
||||
from tgbot.database import (
|
||||
Categoryx,
|
||||
Positionx,
|
||||
Itemx,
|
||||
Purchasesx,
|
||||
Refillx,
|
||||
Settingsx,
|
||||
Userx,
|
||||
ModelPurchases,
|
||||
ModelRefill,
|
||||
ModelUser,
|
||||
)
|
||||
from tgbot.keyboards.inline_admin import profile_edit_finl
|
||||
from tgbot.keyboards.inline_admin_products import position_edit_open_finl, category_edit_open_finl, item_delete_finl
|
||||
from tgbot.keyboards.inline_user import user_profile_finl
|
||||
from tgbot.keyboards.inline_user_products import products_open_finl
|
||||
from tgbot.services.api_hosting_text import HostingAPI
|
||||
from tgbot.utils.const_functions import ded, get_unix, convert_day, convert_date
|
||||
from tgbot.utils.misc.bot_models import ARS
|
||||
|
||||
|
||||
################################################################################
|
||||
################################# ПОЛЬЗОВАТЕЛЬ #################################
|
||||
# Открытие профиля пользователем
|
||||
async def open_profile_user(bot: Bot, user_id: Union[int, str]):
|
||||
get_purchases = await Purchasesx().gets(user_id=user_id)
|
||||
get_user = await Userx().get_required(user_id=user_id)
|
||||
|
||||
how_days = int(get_unix() - get_user.user_unix) // 60 // 60 // 24
|
||||
count_items = sum([purchase.purchase_count for purchase in get_purchases])
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>👤 Ваш профиль</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
🆔 ID: <code>{get_user.user_id}</code>
|
||||
💰 Баланс: <code>{get_user.user_balance}₽</code>
|
||||
🎁 Куплено товаров: <code>{count_items}шт</code>
|
||||
|
||||
🕰 Регистрация: <code>{convert_date(get_user.user_unix, False, False)} ({convert_day(how_days)})</code>
|
||||
"""),
|
||||
reply_markup=user_profile_finl(),
|
||||
)
|
||||
|
||||
|
||||
# Открытие позиции пользователем
|
||||
async def position_open_user(bot: Bot, user_id: int, position_id: int, remover: int):
|
||||
get_items = await Itemx().gets(position_id=position_id)
|
||||
get_position = await Positionx().get_required(position_id=position_id)
|
||||
get_category = await Categoryx().get_required(category_id=get_position.category_id)
|
||||
|
||||
if get_position.position_desc != "None":
|
||||
text_desc = f"▪️ Описание: {get_position.position_desc}"
|
||||
else:
|
||||
text_desc = ""
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>🎁 Покупка товара</b>{hide_link(get_position.position_photo)}
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Название: <code>{get_position.position_name}</code>
|
||||
▪️ Категория: <code>{get_category.category_name}</code>
|
||||
▪️ Стоимость: <code>{get_position.position_price}₽</code>
|
||||
▪️ Количество: <code>{len(get_items)}шт</code>
|
||||
{text_desc}
|
||||
"""),
|
||||
link_preview_options=LinkPreviewOptions(show_above_text=True),
|
||||
reply_markup=products_open_finl(position_id, get_position.category_id, remover),
|
||||
|
||||
)
|
||||
|
||||
|
||||
################################################################################
|
||||
#################################### АДМИН #####################################
|
||||
# Открытие профиля админом
|
||||
async def open_profile_admin(bot: Bot, user_id: int, get_user: ModelUser):
|
||||
get_purchases = await Purchasesx().gets(user_id=get_user.user_id)
|
||||
|
||||
how_days = int(get_unix() - get_user.user_unix) // 60 // 60 // 24
|
||||
count_items = sum([purchase.purchase_count for purchase in get_purchases])
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>👤 Профиль пользователя: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a></b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ ID: <code>{get_user.user_id}</code>
|
||||
▪️ Логин: <b>@{get_user.user_login}</b>
|
||||
▪️ Имя: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a>
|
||||
▪️ Регистрация: <code>{convert_date(get_user.user_unix, False, False)} ({convert_day(how_days)})</code>
|
||||
|
||||
▪️ Баланс: <code>{get_user.user_balance}₽</code>
|
||||
▪️ Всего выдано: <code>{get_user.user_give}₽</code>
|
||||
▪️ Всего пополнено: <code>{get_user.user_refill}₽</code>
|
||||
▪️ Куплено товаров: <code>{count_items}шт</code>
|
||||
"""),
|
||||
reply_markup=profile_edit_finl(get_user.user_id),
|
||||
)
|
||||
|
||||
|
||||
# Открытие пополнения админом
|
||||
async def refill_open_admin(bot: Bot, user_id: int, get_refill: ModelRefill):
|
||||
get_user = await Userx().get_required(user_id=get_refill.user_id)
|
||||
|
||||
if get_refill.refill_method in ['Form', 'Nickname', 'Number', 'QIWI']:
|
||||
pay_method = "QIWI 🥝"
|
||||
elif get_refill.refill_method == "Yoomoney":
|
||||
pay_method = "ЮMoney 🔮"
|
||||
elif get_refill.refill_method == "Cryptobot":
|
||||
pay_method = "CryptoBot 🔷"
|
||||
else:
|
||||
pay_method = f"{get_refill.refill_method}"
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>🧾 Чек: <code>#{get_refill.refill_receipt}</code></b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Пользователь: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||
▪️ Сумма пополнения: <code>{get_refill.refill_amount}₽</code>
|
||||
▪️ Способ пополнения: <code>{pay_method}</code>
|
||||
▪️ Комментарий: <code>{get_refill.refill_comment}</code>
|
||||
▪️ Дата пополнения: <code>{convert_date(get_refill.refill_unix)}</code>
|
||||
"""),
|
||||
)
|
||||
|
||||
|
||||
# Открытие покупки админом
|
||||
async def purchase_open_admin(bot: Bot, arSession: ARS, user_id: int, get_purchase: ModelPurchases):
|
||||
get_user = await Userx().get_required(user_id=get_purchase.user_id)
|
||||
|
||||
link_items = await (
|
||||
await HostingAPI.connect(
|
||||
bot=bot,
|
||||
arSession=arSession,
|
||||
)
|
||||
).upload_text(get_purchase.purchase_data)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>🧾 Чек: <code>#{get_purchase.purchase_receipt}</code></b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Пользователь: <a href='tg://user?id={get_user.user_id}'>{get_user.user_name}</a> | <code>{get_user.user_id}</code>
|
||||
|
||||
▪️ Название товара: <code>{get_purchase.purchase_position_name}</code>
|
||||
▪️ Куплено товаров: <code>{get_purchase.purchase_count}шт</code>
|
||||
▪️ Цена одного товара: <code>{get_purchase.purchase_price_one}₽</code>
|
||||
▪️ Сумма покупки: <code>{get_purchase.purchase_price}₽</code>
|
||||
|
||||
▪️ Баланс до покупки: <code>{get_purchase.user_balance_before}₽</code>
|
||||
▪️ Баланс после покупки: <code>{get_purchase.user_balance_after}₽</code>
|
||||
|
||||
▪️ Товары: <a href='{link_items}'>кликабельно</a>
|
||||
▪️ Дата покупки: <code>{convert_date(get_purchase.purchase_unix)}</code>
|
||||
"""),
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
|
||||
# Открытие категории админом
|
||||
async def category_open_admin(bot: Bot, user_id: int, category_id: int, remover: int):
|
||||
profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = 0, 0, 0, 0
|
||||
profit_count_all, profit_count_day, profit_count_week, profit_count_month = 0, 0, 0, 0
|
||||
|
||||
get_items = await Itemx().gets(category_id=category_id)
|
||||
get_category = await Categoryx().get_required(category_id=category_id)
|
||||
get_positions = await Positionx().gets(category_id=category_id)
|
||||
|
||||
get_purchases = await Purchasesx().gets(purchase_category_id=category_id)
|
||||
get_settings = await Settingsx().get()
|
||||
|
||||
for purchase in get_purchases:
|
||||
profit_amount_all += purchase.purchase_price
|
||||
profit_count_all += purchase.purchase_count
|
||||
|
||||
if purchase.purchase_unix - get_settings.misc_profit_day >= 0:
|
||||
profit_amount_day += purchase.purchase_price
|
||||
profit_count_day += purchase.purchase_count
|
||||
if purchase.purchase_unix - get_settings.misc_profit_week >= 0:
|
||||
profit_amount_week += purchase.purchase_price
|
||||
profit_count_week += purchase.purchase_count
|
||||
if purchase.purchase_unix - get_settings.misc_profit_month >= 0:
|
||||
profit_amount_month += purchase.purchase_price
|
||||
profit_count_month += purchase.purchase_count
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>🗃️ Редактирование категории</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Категория: <code>{get_category.category_name}</code>
|
||||
▪️ Кол-во позиций: <code>{len(get_positions)}шт</code>
|
||||
▪️ Кол-во товаров: <code>{len(get_items)}шт</code>
|
||||
▪️ Дата создания: <code>{convert_date(get_category.category_unix)}шт</code>
|
||||
|
||||
💸 Продаж за День: <code>{profit_count_day}шт</code> - <code>{round(profit_amount_day, 2)}₽</code>
|
||||
💸 Продаж за Неделю: <code>{profit_count_week}шт</code> - <code>{round(profit_amount_week, 2)}₽</code>
|
||||
💸 Продаж за Месяц: <code>{profit_count_month}шт</code> - <code>{round(profit_amount_month, 2)}₽</code>
|
||||
💸 Продаж за Всё время: <code>{profit_count_all}шт</code> - <code>{round(profit_amount_all, 2)}₽</code>
|
||||
"""),
|
||||
reply_markup=await category_edit_open_finl(bot, category_id, remover),
|
||||
)
|
||||
|
||||
|
||||
# Открытие позиции админом
|
||||
async def position_open_admin(bot: Bot, position_id: int, user_id: int):
|
||||
profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = 0, 0, 0, 0
|
||||
profit_count_all, profit_count_day, profit_count_week, profit_count_month = 0, 0, 0, 0
|
||||
|
||||
get_items = await Itemx().gets(position_id=position_id)
|
||||
get_position = await Positionx().get_required(position_id=position_id)
|
||||
get_category = await Categoryx().get_required(category_id=get_position.category_id)
|
||||
|
||||
get_settings = await Settingsx().get()
|
||||
get_purchases = await Purchasesx().gets(purchase_position_id=position_id)
|
||||
|
||||
# Наличие фото
|
||||
if get_position.position_photo != "None":
|
||||
position_photo_text = "<code>Присутствует ✅</code>"
|
||||
else:
|
||||
position_photo_text = "<code>Отсутствует ❌</code>"
|
||||
|
||||
# Наличие описания
|
||||
if get_position.position_desc != "None":
|
||||
position_desc = f"{get_position.position_desc}"
|
||||
else:
|
||||
position_desc = "<code>Отсутствует ❌</code>"
|
||||
|
||||
# Статистика позиции
|
||||
for purchase in get_purchases:
|
||||
profit_amount_all += purchase.purchase_price
|
||||
profit_count_all += purchase.purchase_count
|
||||
|
||||
if purchase.purchase_unix - get_settings.misc_profit_day >= 0:
|
||||
profit_amount_day += purchase.purchase_price
|
||||
profit_count_day += purchase.purchase_count
|
||||
if purchase.purchase_unix - get_settings.misc_profit_week >= 0:
|
||||
profit_amount_week += purchase.purchase_price
|
||||
profit_count_week += purchase.purchase_count
|
||||
if purchase.purchase_unix - get_settings.misc_profit_month >= 0:
|
||||
profit_amount_month += purchase.purchase_price
|
||||
profit_count_month += purchase.purchase_count
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>📁 Редактирование позиции</b>{hide_link(get_position.position_photo)}
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Категория: <code>{get_category.category_name}</code>
|
||||
▪️ Позиция: <code>{get_position.position_name}</code>
|
||||
▪️ Стоимость: <code>{get_position.position_price}₽</code>
|
||||
▪️ Количество: <code>{len(get_items)}шт</code>
|
||||
▪️ Дата создания: <code>{convert_date(get_category.category_unix)}</code>
|
||||
▪️ Изображение: {position_photo_text}
|
||||
▪️ Описание: {position_desc}
|
||||
|
||||
💸 Продаж за День: <code>{profit_count_day}шт</code> - <code>{round(profit_amount_day, 2)}₽</code>
|
||||
💸 Продаж за Неделю: <code>{profit_count_week}шт</code> - <code>{round(profit_amount_week, 2)}₽</code>
|
||||
💸 Продаж за Месяц: <code>{profit_count_month}шт</code> - <code>{round(profit_amount_month, 2)}₽</code>
|
||||
💸 Продаж за Всё время: <code>{profit_count_all}шт</code> - <code>{round(profit_amount_all, 2)}₽</code>
|
||||
"""),
|
||||
link_preview_options=LinkPreviewOptions(show_above_text=True),
|
||||
reply_markup=await position_edit_open_finl(bot, position_id, 0),
|
||||
|
||||
)
|
||||
|
||||
|
||||
# Открытие товара админом
|
||||
async def item_open_admin(bot: Bot, item_id: int, user_id: int):
|
||||
get_item = await Itemx().get_required(item_id=item_id)
|
||||
|
||||
get_position = await Positionx().get_required(position_id=get_item.position_id)
|
||||
get_category = await Categoryx().get_required(category_id=get_item.category_id)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=user_id,
|
||||
text=ded(f"""
|
||||
<b>🎁️ Редактирование товара</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
▪️ Категория: <code>{get_category.category_name}</code>
|
||||
▪️ Позиция: <code>{get_position.position_name}</code>
|
||||
▪️ Дата добавления: <code>{convert_date(get_item.item_unix)}</code>
|
||||
▪️ Товар: <code>{get_item.item_data}</code>
|
||||
"""),
|
||||
reply_markup=item_delete_finl(get_item.item_id, get_item.position_id),
|
||||
)
|
||||
|
||||
|
||||
################################################################################
|
||||
################################################################################
|
||||
# Статистика бота
|
||||
async def get_statistics() -> str:
|
||||
refill_amount_all, refill_amount_day, refill_amount_week, refill_amount_month = 0, 0, 0, 0
|
||||
refill_count_all, refill_count_day, refill_count_week, refill_count_month = 0, 0, 0, 0
|
||||
profit_amount_all, profit_amount_day, profit_amount_week, profit_amount_month = 0, 0, 0, 0
|
||||
profit_count_all, profit_count_day, profit_count_week, profit_count_month = 0, 0, 0, 0
|
||||
users_all, users_day, users_week, users_month, users_money_have, users_money_give = 0, 0, 0, 0, 0, 0
|
||||
refill_cryptobot_count, refill_cryptobot_amount = 0, 0
|
||||
refill_yoomoney_count, refill_yoomoney_amount = 0, 0
|
||||
refill_stars_count, refill_stars_amount = 0, 0
|
||||
|
||||
get_categories = await Categoryx().get_all()
|
||||
get_positions = await Positionx().get_all()
|
||||
get_purchases = await Purchasesx().get_all()
|
||||
get_refill = await Refillx().get_all()
|
||||
get_items = await Itemx().get_all()
|
||||
get_users = await Userx().get_all()
|
||||
get_settings = await Settingsx().get()
|
||||
|
||||
# Покупки
|
||||
for purchase in get_purchases:
|
||||
profit_amount_all += purchase.purchase_price
|
||||
profit_count_all += purchase.purchase_count
|
||||
|
||||
if purchase.purchase_unix - get_settings.misc_profit_day >= 0:
|
||||
profit_amount_day += purchase.purchase_price
|
||||
profit_count_day += purchase.purchase_count
|
||||
if purchase.purchase_unix - get_settings.misc_profit_week >= 0:
|
||||
profit_amount_week += purchase.purchase_price
|
||||
profit_count_week += purchase.purchase_count
|
||||
if purchase.purchase_unix - get_settings.misc_profit_month >= 0:
|
||||
profit_amount_month += purchase.purchase_price
|
||||
profit_count_month += purchase.purchase_count
|
||||
|
||||
# Пополнения
|
||||
for refill in get_refill:
|
||||
refill_amount_all += refill.refill_amount
|
||||
refill_count_all += 1
|
||||
|
||||
if refill.refill_method == "Yoomoney":
|
||||
refill_yoomoney_count += 1
|
||||
refill_yoomoney_amount += refill.refill_amount
|
||||
elif refill.refill_method == "Cryptobot":
|
||||
refill_cryptobot_count += 1
|
||||
refill_cryptobot_amount += refill.refill_amount
|
||||
elif refill.refill_method == "Stars":
|
||||
refill_stars_count += 1
|
||||
refill_stars_amount += refill.refill_amount
|
||||
|
||||
if refill.refill_unix - get_settings.misc_profit_day >= 0:
|
||||
refill_amount_day += refill.refill_amount
|
||||
refill_count_day += 1
|
||||
if refill.refill_unix - get_settings.misc_profit_week >= 0:
|
||||
refill_amount_week += refill.refill_amount
|
||||
refill_count_week += 1
|
||||
if refill.refill_unix - get_settings.misc_profit_month >= 0:
|
||||
refill_amount_month += refill.refill_amount
|
||||
refill_count_month += 1
|
||||
|
||||
# Пользователи и средства
|
||||
for user in get_users:
|
||||
users_money_have += user.user_balance
|
||||
users_money_give += user.user_give
|
||||
users_all += 1
|
||||
|
||||
if user.user_unix - get_settings.misc_profit_day >= 0:
|
||||
users_day += 1
|
||||
if user.user_unix - get_settings.misc_profit_week >= 0:
|
||||
users_week += 1
|
||||
if user.user_unix - get_settings.misc_profit_month >= 0:
|
||||
users_month += 1
|
||||
|
||||
# Даты обновления статистики
|
||||
all_days = [
|
||||
'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье',
|
||||
]
|
||||
|
||||
all_months = [
|
||||
'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь',
|
||||
'Октябрь', 'Ноябрь', 'Декабрь'
|
||||
]
|
||||
|
||||
now_day = datetime.now().day
|
||||
now_week = datetime.now().weekday()
|
||||
now_month = datetime.now().month
|
||||
now_year = datetime.now().year
|
||||
|
||||
unix_day = int(datetime.strptime(f"{now_day}.{now_month}.{now_year} 0:0:0", "%d.%m.%Y %H:%M:%S").timestamp())
|
||||
unix_week = unix_day - (now_week * 86400)
|
||||
|
||||
week_day = int(datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%d"))
|
||||
week_month = int(datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).strftime("%m"))
|
||||
week_week = int(datetime.fromtimestamp(unix_week, pytz.timezone(BOT_TIMEZONE)).weekday())
|
||||
|
||||
return ded(f"""
|
||||
<b>📊 СТАТИСТИКА БОТА</b>
|
||||
➖➖➖➖➖➖➖➖➖➖
|
||||
<b>👤 Пользователи</b>
|
||||
┣ Юзеров за День: <code>{users_day}</code>
|
||||
┣ Юзеров за Неделю: <code>{users_week}</code>
|
||||
┣ Юзеров за Месяц: <code>{users_month}</code>
|
||||
┗ Юзеров за Всё время: <code>{users_all}</code>
|
||||
|
||||
<b>💰 Средства</b>
|
||||
┣‒ Продажи (кол-во, сумма)
|
||||
┣ За День: <code>{profit_count_day}шт</code> - <code>{round(profit_amount_day, 2)}₽</code>
|
||||
┣ За Неделю: <code>{profit_count_week}шт</code> - <code>{round(profit_amount_week, 2)}₽</code>
|
||||
┣ За Месяц: <code>{profit_count_month}шт</code> - <code>{round(profit_amount_month, 2)}₽</code>
|
||||
┣ За Всё время: <code>{profit_count_all}шт</code> - <code>{round(profit_amount_all, 2)}₽</code>
|
||||
┃
|
||||
┣‒ Пополнения (кол-во, сумма)
|
||||
┣ За День: <code>{refill_count_day}шт</code> - <code>{round(refill_amount_day, 2)}₽</code>
|
||||
┣ За Неделю: <code>{refill_count_week}шт</code> - <code>{round(refill_amount_week, 2)}₽</code>
|
||||
┣ За Месяц: <code>{refill_count_month}шт</code> - <code>{round(refill_amount_month, 2)}₽</code>
|
||||
┣ За Всё время: <code>{refill_count_all}шт</code> - <code>{round(refill_amount_all, 2)}₽</code>
|
||||
┃
|
||||
┣‒ Платежные системы (всего)
|
||||
┣ CryptoBot: <code>{refill_cryptobot_count}шт</code> - <code>{round(refill_cryptobot_amount, 2)}₽</code>
|
||||
┣ TG Stars: <code>{refill_stars_count}шт</code> - <code>{round(refill_stars_amount, 2)}₽</code>
|
||||
┣ ЮMoney: <code>{refill_yoomoney_count}шт</code> - <code>{round(refill_yoomoney_amount, 2)}₽</code>
|
||||
┃
|
||||
┣‒ Остальные
|
||||
┣ Средств выдано: <code>{round(users_money_give, 2)}₽</code>
|
||||
┗ Средств в системе: <code>{round(users_money_have, 2)}₽</code>
|
||||
|
||||
<b>🎁 Товары</b>
|
||||
┣ Товаров: <code>{len(get_items)}шт</code>
|
||||
┣ Позиций: <code>{len(get_positions)}шт</code>
|
||||
┗ Категорий: <code>{len(get_categories)}шт</code>
|
||||
|
||||
<b>🕰 Даты статистики</b>
|
||||
┣ Дневная: <code>{now_day} {all_months[now_month - 1].title()}</code>
|
||||
┣ Недельная: <code>{week_day} {all_months[week_month - 1].title()}, {all_days[week_week]}</code>
|
||||
┗ Месячная: <code>1 {all_months[now_month - 1].title()}, {now_year}г</code>
|
||||
""")
|
||||
Reference in New Issue
Block a user