From f3cdeeff3cf87f5228cc6015983f52f93f77dee5 Mon Sep 17 00:00:00 2001 From: Sergey Elpashev Date: Sun, 11 Feb 2024 12:40:11 +0300 Subject: [PATCH] Refactoring: handlers now implemented as classes. Module structure --- handlers/admin_commands.py | 298 ++---------------- handlers/handler.py | 33 +- handlers/message_handlers/__init__.py | 0 handlers/message_handlers/edit_command.py | 22 ++ handlers/message_handlers/forward_message.py | 34 ++ handlers/message_handlers/handler.py | 26 ++ handlers/message_handlers/info_command.py | 49 +++ handlers/message_handlers/newpost_command.py | 21 ++ handlers/message_handlers/post_command.py | 31 ++ handlers/message_handlers/reply_to_user.py | 18 ++ handlers/message_handlers/settings_command.py | 14 + handlers/message_handlers/start_command.py | 12 + handlers/message_handlers/update_settings.py | 45 +++ handlers/user_commands.py | 39 +-- neuroapi/types/_singleton.py | 4 + 15 files changed, 330 insertions(+), 316 deletions(-) create mode 100644 handlers/message_handlers/__init__.py create mode 100644 handlers/message_handlers/edit_command.py create mode 100644 handlers/message_handlers/forward_message.py create mode 100644 handlers/message_handlers/handler.py create mode 100644 handlers/message_handlers/info_command.py create mode 100644 handlers/message_handlers/newpost_command.py create mode 100644 handlers/message_handlers/post_command.py create mode 100644 handlers/message_handlers/reply_to_user.py create mode 100644 handlers/message_handlers/settings_command.py create mode 100644 handlers/message_handlers/start_command.py create mode 100644 handlers/message_handlers/update_settings.py diff --git a/handlers/admin_commands.py b/handlers/admin_commands.py index 04a35ac..5da6b19 100644 --- a/handlers/admin_commands.py +++ b/handlers/admin_commands.py @@ -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 + ]) diff --git a/handlers/handler.py b/handlers/handler.py index ff4548c..caae808 100644 --- a/handlers/handler.py +++ b/handlers/handler.py @@ -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 \ No newline at end of file + 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) \ No newline at end of file diff --git a/handlers/message_handlers/__init__.py b/handlers/message_handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/handlers/message_handlers/edit_command.py b/handlers/message_handlers/edit_command.py new file mode 100644 index 0000000..532fb3b --- /dev/null +++ b/handlers/message_handlers/edit_command.py @@ -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}') \ No newline at end of file diff --git a/handlers/message_handlers/forward_message.py b/handlers/message_handlers/forward_message.py new file mode 100644 index 0000000..da3a9b4 --- /dev/null +++ b/handlers/message_handlers/forward_message.py @@ -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Но они не смогут вам ответить из-за ваших настроек конфиденциальности.')) \ No newline at end of file diff --git a/handlers/message_handlers/handler.py b/handlers/message_handlers/handler.py new file mode 100644 index 0000000..4a11fbb --- /dev/null +++ b/handlers/message_handlers/handler.py @@ -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 \ No newline at end of file diff --git a/handlers/message_handlers/info_command.py b/handlers/message_handlers/info_command.py new file mode 100644 index 0000000..1d656c3 --- /dev/null +++ b/handlers/message_handlers/info_command.py @@ -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) \ No newline at end of file diff --git a/handlers/message_handlers/newpost_command.py b/handlers/message_handlers/newpost_command.py new file mode 100644 index 0000000..f5d06c1 --- /dev/null +++ b/handlers/message_handlers/newpost_command.py @@ -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('Пост успешно добавлен!') \ No newline at end of file diff --git a/handlers/message_handlers/post_command.py b/handlers/message_handlers/post_command.py new file mode 100644 index 0000000..7caa5f5 --- /dev/null +++ b/handlers/message_handlers/post_command.py @@ -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}') \ No newline at end of file diff --git a/handlers/message_handlers/reply_to_user.py b/handlers/message_handlers/reply_to_user.py new file mode 100644 index 0000000..440c2ab --- /dev/null +++ b/handlers/message_handlers/reply_to_user.py @@ -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}"') \ No newline at end of file diff --git a/handlers/message_handlers/settings_command.py b/handlers/message_handlers/settings_command.py new file mode 100644 index 0000000..9d49288 --- /dev/null +++ b/handlers/message_handlers/settings_command.py @@ -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) \ No newline at end of file diff --git a/handlers/message_handlers/start_command.py b/handlers/message_handlers/start_command.py new file mode 100644 index 0000000..72b0a57 --- /dev/null +++ b/handlers/message_handlers/start_command.py @@ -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Данный бот принимает текст, картинки, документы и стикеры.") + \ No newline at end of file diff --git a/handlers/message_handlers/update_settings.py b/handlers/message_handlers/update_settings.py new file mode 100644 index 0000000..e68a4f7 --- /dev/null +++ b/handlers/message_handlers/update_settings.py @@ -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('Настройки обновлены!') + \ No newline at end of file diff --git a/handlers/user_commands.py b/handlers/user_commands.py index 917502d..b5506b1 100644 --- a/handlers/user_commands.py +++ b/handlers/user_commands.py @@ -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 + ]) \ No newline at end of file diff --git a/neuroapi/types/_singleton.py b/neuroapi/types/_singleton.py index 46d6a87..9561b26 100644 --- a/neuroapi/types/_singleton.py +++ b/neuroapi/types/_singleton.py @@ -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] \ No newline at end of file