Refactoring: handlers now implemented as classes. Module structure

This commit is contained in:
2024-02-11 12:40:11 +03:00
parent c2d7fd41d7
commit f3cdeeff3c
15 changed files with 330 additions and 316 deletions

View File

@@ -1,292 +1,32 @@
import asyncio
from typing import List
from aiogram import Bot
import aioschedule as schedule
from aiogram import Bot, F, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.utils.media_group import MediaGroupBuilder
import neuroapi.types as neuroTypes
from handlers.filters.new_post import (ChangePosts, NewPostFilter,
NewSoloPostFilter)
from handlers.filters.reply_to_user import ReplyToUser
from handlers.handler import Handler
from handlers.message_handlers.edit_command import EditCommand
from handlers.message_handlers.info_command import InfoCommand
from handlers.message_handlers.newpost_command import (NewPostCommand,
NewPostSoloCommand)
from handlers.message_handlers.post_command import PostCommand
from handlers.message_handlers.reply_to_user import ReplyToUserCommand
from handlers.message_handlers.settings_command import SettingsCommand
from handlers.message_handlers.update_settings import UpdateSettingsCommand
from handlers.middlewares.user import AdminMiddleware
from handlers.states.change_post import ChangePost
from neuroapi import neuroapi
from neuroapi.types import BotSettings as BotSettingsType
def get_post_info(post: neuroTypes.Post, post_id: int) -> str:
text = post.text
time = post.timestamp
from_user = post.from_user_id
s = f"""Индекс: {post_id}\nТекст: {text}\nВремя отправки: {time}\nОт: [id{from_user}](tg://user?id={from_user})""".replace('#', '\#').replace(
"_", "\_").replace('.', '\.').replace(',', '\,').replace('!', '\!').replace('-', '\-').replace(':', '\:').replace('+', '\+')
return s
class AdminCommands(Handler):
settings: BotSettingsType
def __init__(self, bot: Bot) -> None:
super().__init__(bot)
self.router.message.middleware(AdminMiddleware())
@self.router.message(NewPostFilter())
async def new_post(message: types.Message):
post: neuroTypes.Post = await neuroapi.post.get_by_media_group_id(message.media_group_id)
await neuroapi.image.add(str(post.uuid), message.photo[-1].file_id, message.has_media_spoiler, message.message_id)
@self.router.message(Command('info'))
async def info_command(message: types.Message):
posts: List[neuroTypes.Post] = await neuroapi.post.get_will_post()
admins: List[neuroTypes.Admin] = await neuroapi.admin.get()
post_c = {}
k = 1
for post in posts:
if post.from_user_id not in post_c:
post_c[post.from_user_id] = 1
else:
post_c[post.from_user_id] += 1
res = "Количество постов от админов:\n"
res2 = "\nПосты:\n"
posts_entities: List[types.MessageEntity] = []
for admin in admins:
if admin.user_id in post_c:
res += f'[{admin.user_name}](tg://user?id={admin.user_id}): {post_c[admin.user_id]}\n'
else:
res += f'[{admin.user_name}](tg://user?id={admin.user_id}): 0\n'
admin_posts = list(
filter(lambda x: x.from_user_id == admin.user_id, posts))
res2 += f'\nПосты от {admin.user_name}:\n'
if len(admin_posts):
for i, post in enumerate(admin_posts):
# TODO: Если возможно, сделать чтоб было ссылкой на сообщений с /newpost
s = f'{i+1}.({posts.index(post)+1}) {post.text}\n'
k+=1
res2 += s
for entity in post.message_entities:
entity.offset += 6+res2.index(s)
posts_entities.append(entity)
else:
res2 += 'Их нет)\n'
await message.answer(res.replace('#', '\#').replace(
"_", "\_").replace('.', '\.').replace(',', '\,').replace('!', '\!').replace('-', '\-').replace(':', '\:').replace('+', '\+'), parse_mode='markdownv2')
await message.answer(res2, entities=posts_entities)
@self.router.message(Command('edit'))
async def edit_post_by_order_num(message: types.Message):
command = message.text.split(' ', 2)
if len(command)<3:
await message.reply('Недостаточно аргументов!')
return
try:
await neuroapi.post.edit_text_by_order_num(command[1], command[2], message.entities)
#TODO: Message Entities для уведомления об изменении поста
await message.reply(f'Текст поста успешно изменен на: {command[2]}')
except Exception as e:
await message.reply(f'Ошибка: {e}')
"""
TODO: Изменение постов сделать нормально, не через редактирование сообщений
@self.router.message(ChangePosts())
async def change_post(message: types.Message, state: FSMContext):
posts = await neuroapi.post.get_will_post()
if (posts):
await state.update_data(posts=posts, id=0)
select_btns = []
if len(posts) > 1:
select_btns.append(types.InlineKeyboardButton(
text='->', callback_data='next_post'))
kb = [
select_btns,
[types.InlineKeyboardButton(
callback_data='change_post_text', text='Текст')],
[types.InlineKeyboardButton(
text='Отмена', callback_data='cancel')]
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=kb)
post = await neuroapi.post.get(str(posts[0].uuid))
images = MediaGroupBuilder(
caption=get_post_info(post, 1))
image: neuroTypes.Image
for image in sorted(post.images, key=lambda x: x.message_id):
images.add_photo(image.file_id,
has_spoiler=image.has_spoiler, parse_mode='markdownv2')
mes = await message.answer_media_group(images.build())
await state.update_data(edit_msg=mes[0].message_id)
await message.answer('Действия', reply_markup=keyboard)
else:
await message.answer('Нет постов')
@self.router.callback_query(F.data == 'next_post')
async def next_post_changing(callback: types.CallbackQuery, state: FSMContext):
data = await state.get_data()
if 'posts' not in data:
await state.clear()
await callback.answer()
await callback.message.delete()
return
posts: List[neuroTypes.Post] = data['posts']
post_id = data['id']+1
select_btns = [types.InlineKeyboardButton(
text='<-', callback_data='prev_post')]
if post_id < len(posts)-1:
select_btns.append(types.InlineKeyboardButton(
text='->', callback_data='next_post'))
kb = [
select_btns,
[types.InlineKeyboardButton(
callback_data='change_post_text', text='Текст')],
[types.InlineKeyboardButton(
text='Отмена', callback_data='cancel')]
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=kb)
await state.update_data(id=post_id)
post = await neuroapi.post.get(str(posts[post_id].uuid))
await bot.edit_message_caption(caption=get_post_info(post, post_id+1), chat_id=callback.message.chat.id, message_id=data['edit_msg'], parse_mode='markdownv2')
await callback.message.edit_reply_markup(reply_markup=keyboard)
await callback.answer()
@self.router.callback_query(F.data == 'change_post_text')
async def change_post_text_call(callback: types.CallbackQuery, state: FSMContext):
data = await state.get_data()
if 'posts' not in data:
await state.clear()
await callback.answer()
await callback.message.delete()
return
await callback.message.delete()
await callback.answer()
kb = [
[types.InlineKeyboardButton(
text='Отмена', callback_data='cancel')]
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=kb)
await state.set_state(ChangePost.Text)
await callback.message.answer('Введите новый текст поста:', reply_markup=keyboard)
@self.router.message(ChangePost.Text)
async def change_post_text(message: types.Message, state: FSMContext):
data = await state.get_data()
if 'posts' not in data:
await state.clear()
return
posts: List[neuroTypes.Post] = data['posts']
post_id = data['id']
post_uuid = str(posts[post_id].uuid)
try:
await neuroapi.post.edit_text(post_uuid, message.text)
await message.answer(f'Текст поста изменен на: {message.text}')
except:
await message.answer('Ошибка')
await state.clear()
@self.router.callback_query(F.data == 'prev_post')
async def prev_post_changing(callback: types.CallbackQuery, state: FSMContext):
data = await state.get_data()
if 'posts' not in data:
await state.clear()
await callback.answer()
await callback.message.delete()
return
posts: List[neuroTypes.Post] = data['posts']
post_id = data['id']-1
select_btns = [types.InlineKeyboardButton(
text='->', callback_data='next_post')]
if post_id > 0:
select_btns = [types.InlineKeyboardButton(
text='<-', callback_data='prev_post'), *select_btns]
kb = [
select_btns,
[types.InlineKeyboardButton(
callback_data='change_post_text', text='Текст')],
[types.InlineKeyboardButton(
text='Отмена', callback_data='cancel')]
]
keyboard = types.InlineKeyboardMarkup(inline_keyboard=kb)
await state.update_data(id=post_id)
post = await neuroapi.post.get(str(posts[post_id].uuid))
await bot.edit_message_caption(caption=get_post_info(post, post_id), chat_id=callback.message.chat.id, message_id=data['edit_msg'], parse_mode='markdownv2')
await callback.message.edit_reply_markup(reply_markup=keyboard)
await callback.answer()
@self.router.callback_query(F.data.casefold() == 'cancel')
async def cancel_changing(callback: types.CallbackQuery, state: FSMContext):
await state.clear()
await callback.answer()
await callback.message.delete()
data = await state.get_data()
if 'edit_msg' in data:
await bot.delete_message(message_id=data['edit_msg'], chat_id=callback.message.chat.id)
"""
@self.router.message(Command('post'))
async def post(message: types.Message | None = None):
try:
post = await neuroapi.post.get_post_to_post()
if (post):
images = MediaGroupBuilder(
caption=post.text + '\n\nПредложка: @neur0w0men_reply_bot', caption_entities=post.message_entities)
image: neuroTypes.Image
for image in sorted(post.images, key=lambda x: x.message_id):
images.add_photo(image.file_id,
has_spoiler=image.has_spoiler)
await self.bot.send_media_group(self.settings.channel, images.build())
if message:
await message.answer('Пост успешно опубликован!')
elif message:
await message.answer('Нет постов')
except Exception as e:
if message:
await message.answer(f'Ошибка {e}')
@self.router.message(NewSoloPostFilter())
async def post_solo(message: types.Message):
post: neuroTypes.Post = await neuroapi.post.new(message.caption.replace('/newpost ', ''), message.from_user.id, message_entities=message.caption_entities)
await neuroapi.image.add(str(post.uuid), message.photo[-1].file_id, message.has_media_spoiler, message.message_id)
await message.answer('Пост успешно добавлен!')
@self.router.message(ReplyToUser())
async def reply_user(message: types.Message):
if message.reply_to_message.forward_from is None:
await message.reply('Пользователь стесняшка и не разрешает отвечать на его сообщения...')
else:
try:
await bot.send_message(message.reply_to_message.forward_from.id, f'Вам ответил админ:\n{message.text}', entities=message.entities)
await message.reply('Ваше сообщение было отправлено!')
except Exception as e:
print(e)
@self.router.message(Command('settings'))
async def info_settings(mes: types.Message):
s = f"Текущие настройки:\nКанал: {self.settings.channel}\nВремя: {', '.join(self.settings.message_times)}"
await mes.answer(s)
@self.router.message(Command('update_settings'))
async def update_settings(mes: types.Message | None = None):
self.settings = await neuroapi.bot_settings.get()
schedule.clear()
schedule.every().minute.do(update_settings, None)
# TODO: Сделать в бэке и в боте, чтоб дни тоже можно было в настройках хранить
for i in self.settings.message_times:
schedule.every().monday.at(i).do(post, None)
schedule.every().tuesday.at(i).do(post, None)
schedule.every().wednesday.at(i).do(post, None)
schedule.every().thursday.at(i).do(post, None)
schedule.every().friday.at(i).do(post, None)
schedule.every().sunday.at(i).do(post, None)
if mes:
await mes.answer('Настройки обновлены!')
async def settings_and_schedule_checker():
await update_settings()
while 1:
await schedule.run_pending()
await asyncio.sleep(1)
asyncio.create_task(settings_and_schedule_checker())
self.add_handlers([
InfoCommand,
(UpdateSettingsCommand, PostCommand(self.bot).handler),
EditCommand,
NewPostCommand,
NewPostSoloCommand,
PostCommand,
SettingsCommand,
ReplyToUserCommand
])

View File

@@ -1,16 +1,39 @@
from typing import Any
from typing import Any, Dict, List, Optional, Tuple
from aiogram import Bot, Router
from aiogram.filters import Filter
from .message_handlers.handler import MessageHandlerABC
class NeuroApiRouter(Router):
bot: Bot
def __init__(self, *, name: str | None = None, bot: Bot) -> None:
super().__init__(name=name)
self.bot = bot
def add_message_handler(self, callback: MessageHandlerABC, *args: Any):
handler = callback(self.bot, *args)
self.message.register(handler.handler, handler.filter)
class Handler:
bot: Bot
router: Router
router: NeuroApiRouter
def __init__(self, bot: Bot) -> None:
assert isinstance(bot, Bot)
self.bot = bot
self.router = Router()
self.router = NeuroApiRouter(bot=bot)
def __call__(self) -> Router:
return self.router
def __call__(self) -> NeuroApiRouter:
return self.router
def add_handlers(self, handlers: List[MessageHandlerABC] | List[Tuple[MessageHandlerABC] | Optional[Tuple[Any, ...]]]):
for handler in handlers:
if isinstance(handler, tuple):
args = handler[1:] if len(handler)>1 else []
self.router.add_message_handler(handler[0], *args)
else:
self.router.add_message_handler(handler)

View File

View File

@@ -0,0 +1,22 @@
from aiogram import types
from aiogram.filters import Command
from neuroapi import neuroapi
from .handler import MessageHandlerABC
class EditCommand(MessageHandlerABC):
filter = Command('edit')
async def _command(self, message: types.Message):
command = message.text.split(' ', 2)
if len(command)<3:
await message.reply('Недостаточно аргументов!')
return
try:
await neuroapi.post.edit_text_by_order_num(command[1], command[2], message.entities)
#TODO: Message Entities для уведомления об изменении поста
await message.reply(f'Текст поста успешно изменен на: {command[2]}')
except Exception as e:
await message.reply(f'Ошибка: {e}')

View File

@@ -0,0 +1,34 @@
from typing import List
from aiogram import F, types
from aiogram.enums import ChatMemberStatus
from neuroapi import neuroapi
from neuroapi.types import Admin as AdminType
from neuroapi.types import BotSettings
from .handler import MessageHandlerABC
class ForwardMessageCommand(MessageHandlerABC):
filter = F.chat.type == 'private'
async def _command(self, message: types.Message):
self.settings = BotSettings.get_instance()
user = await self.bot.get_chat_member(self.settings.channel, message.from_user.id)
if user is None:
await message.reply('Ошибка')
return
user_in_channel = user.status == ChatMemberStatus.LEFT
admins: List[AdminType] = await neuroapi.admin.get()
canReply = True
for admin in admins:
await self.bot.send_message(admin.user_id, f'Вам новое сообщение от пользователя {message.from_user.full_name}. ' +
(f'\nНик: @{message.from_user.username}' if message.from_user.username else f'ID: {message.from_user.id}') +
f'\nПользователь{" не " if user_in_channel else " "}состоит в канале')
try:
forwarded_message = await self.bot.forward_message(admin.user_id, message.chat.id, message.message_id)
if forwarded_message.forward_from is None:
canReply = False
except:
pass
await message.reply('Ваше сообщение было отправлено администраторам'+('' if canReply else '\nНо они не смогут вам ответить из-за ваших настроек конфиденциальности.'))

View File

@@ -0,0 +1,26 @@
from abc import ABC, abstractmethod
from typing import Any, Coroutine, Dict
from aiogram import Bot
from aiogram.filters import Filter
class MessageHandlerABC(ABC):
bot: Bot
def __init__(self, bot: Bot, *args: Any, **kwargs: Dict[str, Any]) -> None:
assert isinstance(bot, Bot)
self.bot = bot
@abstractmethod
def _command(self, *args, **kwargs):
raise NotImplementedError
@property
def handler(self) -> Coroutine[None, None, None]:
return self._command
@property
@abstractmethod
def filter(self) -> Filter:
raise NotImplementedError

View File

@@ -0,0 +1,49 @@
from typing import List
from aiogram import types
from aiogram.filters import Command
import neuroapi.types as neuroTypes
from neuroapi import neuroapi
from .handler import MessageHandlerABC
class InfoCommand(MessageHandlerABC):
filter = Command('info')
async def _command(self, message: types.Message):
posts: List[neuroTypes.Post] = await neuroapi.post.get_will_post()
admins: List[neuroTypes.Admin] = await neuroapi.admin.get()
post_c = {}
k = 1
for post in posts:
if post.from_user_id not in post_c:
post_c[post.from_user_id] = 1
else:
post_c[post.from_user_id] += 1
res = "Количество постов от админов:\n"
res2 = "\nПосты:\n"
posts_entities: List[types.MessageEntity] = []
for admin in admins:
if admin.user_id in post_c:
res += f'[{admin.user_name}](tg://user?id={admin.user_id}): {post_c[admin.user_id]}\n'
else:
res += f'[{admin.user_name}](tg://user?id={admin.user_id}): 0\n'
admin_posts = list(
filter(lambda x: x.from_user_id == admin.user_id, posts))
res2 += f'\nПосты от {admin.user_name}:\n'
if len(admin_posts):
for i, post in enumerate(admin_posts):
# TODO: Если возможно, сделать чтоб было ссылкой на сообщений с /newpost
s = f'{i+1}.({posts.index(post)+1}) {post.text}\n'
k+=1
res2 += s
for entity in post.message_entities:
entity.offset += 6+res2.index(s)
posts_entities.append(entity)
else:
res2 += 'Их нет)\n'
await message.answer(res.replace('#', '\#').replace(
"_", "\_").replace('.', '\.').replace(',', '\,').replace('!', '\!').replace('-', '\-').replace(':', '\:').replace('+', '\+'), parse_mode='markdownv2')
await message.answer(res2, entities=posts_entities)

View File

@@ -0,0 +1,21 @@
from aiogram import types
import neuroapi.types as neuroTypes
from handlers.filters.new_post import NewPostFilter, NewSoloPostFilter
from neuroapi import neuroapi
from .handler import MessageHandlerABC
class NewPostCommand(MessageHandlerABC):
filter = NewPostFilter()
async def _command(self, message: types.Message):
post: neuroTypes.Post = await neuroapi.post.get_by_media_group_id(message.media_group_id)
await neuroapi.image.add(str(post.uuid), message.photo[-1].file_id, message.has_media_spoiler, message.message_id)
class NewPostSoloCommand(MessageHandlerABC):
filter = NewSoloPostFilter()
async def _command(self, message: types.Message):
post: neuroTypes.Post = await neuroapi.post.new(message.caption.replace('/newpost ', ''), message.from_user.id, message_entities=message.caption_entities)
await neuroapi.image.add(str(post.uuid), message.photo[-1].file_id, message.has_media_spoiler, message.message_id)
await message.answer('Пост успешно добавлен!')

View File

@@ -0,0 +1,31 @@
from aiogram import types
from aiogram.filters import Command
from aiogram.utils.media_group import MediaGroupBuilder
import neuroapi.types as neuroTypes
from neuroapi import neuroapi
from .handler import MessageHandlerABC
class PostCommand(MessageHandlerABC):
filter = Command('post')
async def _command(self, message: types.Message):
settings = neuroTypes.BotSettings.get_instance()
try:
post = await neuroapi.post.get_post_to_post()
if (post):
images = MediaGroupBuilder(
caption=post.text + '\n\nПредложка: @neur0w0men_reply_bot', caption_entities=post.message_entities)
image: neuroTypes.Image
for image in sorted(post.images, key=lambda x: x.message_id):
images.add_photo(image.file_id,
has_spoiler=image.has_spoiler)
await self.bot.send_media_group(settings.channel, images.build())
if message:
await message.answer('Пост успешно опубликован!')
elif message:
await message.answer('Нет постов')
except Exception as e:
if message:
await message.answer(f'Ошибка {e}')

View File

@@ -0,0 +1,18 @@
from aiogram import types
from handlers.filters.reply_to_user import ReplyToUser
from .handler import MessageHandlerABC
class ReplyToUserCommand(MessageHandlerABC):
filter = ReplyToUser()
async def _command(self, message: types.Message):
if message.reply_to_message.forward_from is None:
await message.reply('Пользователь стесняшка и не разрешает отвечать на его сообщения...')
else:
try:
await self.bot.send_message(message.reply_to_message.forward_from.id, f'Вам ответил админ:\n{message.text}', entities=message.entities)
await message.reply('Ваше сообщение было отправлено!')
except Exception as e:
await message.reply(f'Ошибка! "{e}"')

View File

@@ -0,0 +1,14 @@
from aiogram import types
from aiogram.filters import Command
from neuroapi.types import BotSettings
from .handler import MessageHandlerABC
class SettingsCommand(MessageHandlerABC):
filter = Command('settings')
async def _command(self, message: types.Message):
self.settings = BotSettings.get_instance()
s = f"Текущие настройки:\n{self.settings.get_text()}"
await message.answer(s)

View File

@@ -0,0 +1,12 @@
from aiogram import types
from aiogram.filters import CommandStart
from .handler import MessageHandlerABC
class StartCommand(MessageHandlerABC):
filter = CommandStart()
async def _command(self, message: types.Message):
await message.answer("Добро пожаловать! Данный бот - предложка для канала @neur0w0men. Отправляйте свои пожелания насчет нейрокартинок, а также свои картинки, а админы постараются заняться этим!\nДанный бот принимает текст, картинки, документы и стикеры.")

View File

@@ -0,0 +1,45 @@
import asyncio
from typing import Coroutine
import aioschedule as schedule
from aiogram import Bot, types
from aiogram.filters import Command
from neuroapi import neuroapi
from neuroapi.types import BotSettings
from .handler import MessageHandlerABC
class UpdateSettingsCommand(MessageHandlerABC):
settings: BotSettings
post: Coroutine
filter = Command('update_settings')
async def settings_and_schedule_checker(self):
await self._command()
while 1:
await schedule.run_pending()
await asyncio.sleep(1)
def __init__(self, bot: Bot, post_command: Coroutine, *args) -> None:
super().__init__(bot)
self.post = post_command
asyncio.create_task(self.settings_and_schedule_checker())
async def _command(self, mes: types.Message | None = None):
self.settings = await neuroapi.bot_settings.get()
schedule.clear()
schedule.every().minute.do(self._command, None)
# TODO: Сделать в бэке и в боте, чтоб дни тоже можно было в настройках хранить
for i in self.settings.message_times:
schedule.every().monday.at(i).do(self.post, None)
schedule.every().tuesday.at(i).do(self.post, None)
schedule.every().wednesday.at(i).do(self.post, None)
schedule.every().thursday.at(i).do(self.post, None)
schedule.every().friday.at(i).do(self.post, None)
schedule.every().sunday.at(i).do(self.post, None)
if mes:
await mes.answer('Настройки обновлены!')

View File

@@ -1,12 +1,8 @@
from typing import List
from aiogram import Bot, F, types
from aiogram.enums import ChatMemberStatus
from aiogram.filters import Command, CommandStart
from aiogram import Bot
from handlers.handler import Handler
from neuroapi import neuroapi
from neuroapi.types import Admin as AdminType
from handlers.message_handlers.forward_message import ForwardMessageCommand
from handlers.message_handlers.start_command import StartCommand
from neuroapi.types import BotSettings as BotSettingsType
@@ -16,28 +12,7 @@ class UserCommands(Handler):
def __init__(self, bot: Bot) -> None:
super().__init__(bot)
@self.router.message(CommandStart())
async def start_command(message: types.Message):
await message.answer("Добро пожаловать! Данный бот - предложка для канала @neur0w0men. Отправляйте свои пожелания насчет нейрокартинок, а также свои картинки, а админы постараются заняться этим!\nДанный бот принимает текст, картинки, документы и стикеры.")
@self.router.message(F.chat.type == 'private')
async def forward_post(message: types.Message):
self.settings = BotSettingsType.get_active()
user = await bot.get_chat_member(self.settings.channel, message.from_user.id)
if user is None:
await message.reply('Ошибка')
return
user_in_channel = user.status == ChatMemberStatus.LEFT
admins: List[AdminType] = await neuroapi.admin.get()
canReply = True
for admin in admins:
await bot.send_message(admin.user_id, f'Вам новое сообщение от пользователя {message.from_user.full_name}. ' +
(f'\nНик: @{message.from_user.username}' if message.from_user.username else f'ID: {message.from_user.id}') +
f'\nПользователь{" не " if user_in_channel else " "}состоит в канале')
try:
forwarded_message = await bot.forward_message(admin.user_id, message.chat.id, message.message_id)
if forwarded_message.forward_from is None:
canReply = False
except:
pass
await message.reply('Ваше сообщение было отправлено администраторам'+('' if canReply else '\nНо они не смогут вам ответить из-за ваших настроек конфиденциальности.'))
self.add_handlers([
StartCommand,
ForwardMessageCommand
])

View File

@@ -7,4 +7,8 @@ class Singleton:
def __new__(cls, *args, **kwargs) -> Self:
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__new__(cls)
return cls._instances[cls]
@classmethod
def get_instance(cls: Self):
return cls._instances[cls]