mirror of
https://github.com/MrSedan/neuro-reply-bot-reworked.git
synced 2026-01-14 21:49:42 +03:00
Refactoring: handlers now implemented as classes. Module structure
This commit is contained in:
@@ -1,292 +1,32 @@
|
|||||||
import asyncio
|
from aiogram import Bot
|
||||||
from typing import List
|
|
||||||
|
|
||||||
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.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.middlewares.user import AdminMiddleware
|
||||||
from handlers.states.change_post import ChangePost
|
|
||||||
from neuroapi import neuroapi
|
|
||||||
from neuroapi.types import BotSettings as BotSettingsType
|
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):
|
class AdminCommands(Handler):
|
||||||
settings: BotSettingsType
|
settings: BotSettingsType
|
||||||
|
|
||||||
def __init__(self, bot: Bot) -> None:
|
def __init__(self, bot: Bot) -> None:
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
self.router.message.middleware(AdminMiddleware())
|
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}')
|
|
||||||
|
|
||||||
"""
|
self.add_handlers([
|
||||||
TODO: Изменение постов сделать нормально, не через редактирование сообщений
|
InfoCommand,
|
||||||
@self.router.message(ChangePosts())
|
(UpdateSettingsCommand, PostCommand(self.bot).handler),
|
||||||
async def change_post(message: types.Message, state: FSMContext):
|
EditCommand,
|
||||||
posts = await neuroapi.post.get_will_post()
|
NewPostCommand,
|
||||||
if (posts):
|
NewPostSoloCommand,
|
||||||
await state.update_data(posts=posts, id=0)
|
PostCommand,
|
||||||
select_btns = []
|
SettingsCommand,
|
||||||
if len(posts) > 1:
|
ReplyToUserCommand
|
||||||
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())
|
|
||||||
|
|||||||
@@ -1,16 +1,39 @@
|
|||||||
from typing import Any
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from aiogram import Bot, Router
|
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:
|
class Handler:
|
||||||
bot: Bot
|
bot: Bot
|
||||||
router: Router
|
router: NeuroApiRouter
|
||||||
|
|
||||||
def __init__(self, bot: Bot) -> None:
|
def __init__(self, bot: Bot) -> None:
|
||||||
assert isinstance(bot, Bot)
|
assert isinstance(bot, Bot)
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.router = Router()
|
self.router = NeuroApiRouter(bot=bot)
|
||||||
|
|
||||||
def __call__(self) -> Router:
|
def __call__(self) -> NeuroApiRouter:
|
||||||
return self.router
|
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)
|
||||||
0
handlers/message_handlers/__init__.py
Normal file
0
handlers/message_handlers/__init__.py
Normal file
22
handlers/message_handlers/edit_command.py
Normal file
22
handlers/message_handlers/edit_command.py
Normal 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}')
|
||||||
34
handlers/message_handlers/forward_message.py
Normal file
34
handlers/message_handlers/forward_message.py
Normal 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Но они не смогут вам ответить из-за ваших настроек конфиденциальности.'))
|
||||||
26
handlers/message_handlers/handler.py
Normal file
26
handlers/message_handlers/handler.py
Normal 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
|
||||||
49
handlers/message_handlers/info_command.py
Normal file
49
handlers/message_handlers/info_command.py
Normal 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)
|
||||||
21
handlers/message_handlers/newpost_command.py
Normal file
21
handlers/message_handlers/newpost_command.py
Normal 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('Пост успешно добавлен!')
|
||||||
31
handlers/message_handlers/post_command.py
Normal file
31
handlers/message_handlers/post_command.py
Normal 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}')
|
||||||
18
handlers/message_handlers/reply_to_user.py
Normal file
18
handlers/message_handlers/reply_to_user.py
Normal 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}"')
|
||||||
14
handlers/message_handlers/settings_command.py
Normal file
14
handlers/message_handlers/settings_command.py
Normal 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)
|
||||||
12
handlers/message_handlers/start_command.py
Normal file
12
handlers/message_handlers/start_command.py
Normal 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Данный бот принимает текст, картинки, документы и стикеры.")
|
||||||
|
|
||||||
45
handlers/message_handlers/update_settings.py
Normal file
45
handlers/message_handlers/update_settings.py
Normal 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('Настройки обновлены!')
|
||||||
|
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
from typing import List
|
from aiogram import Bot
|
||||||
|
|
||||||
from aiogram import Bot, F, types
|
|
||||||
from aiogram.enums import ChatMemberStatus
|
|
||||||
from aiogram.filters import Command, CommandStart
|
|
||||||
|
|
||||||
from handlers.handler import Handler
|
from handlers.handler import Handler
|
||||||
from neuroapi import neuroapi
|
from handlers.message_handlers.forward_message import ForwardMessageCommand
|
||||||
from neuroapi.types import Admin as AdminType
|
from handlers.message_handlers.start_command import StartCommand
|
||||||
from neuroapi.types import BotSettings as BotSettingsType
|
from neuroapi.types import BotSettings as BotSettingsType
|
||||||
|
|
||||||
|
|
||||||
@@ -16,28 +12,7 @@ class UserCommands(Handler):
|
|||||||
def __init__(self, bot: Bot) -> None:
|
def __init__(self, bot: Bot) -> None:
|
||||||
super().__init__(bot)
|
super().__init__(bot)
|
||||||
|
|
||||||
@self.router.message(CommandStart())
|
self.add_handlers([
|
||||||
async def start_command(message: types.Message):
|
StartCommand,
|
||||||
await message.answer("Добро пожаловать! Данный бот - предложка для канала @neur0w0men. Отправляйте свои пожелания насчет нейрокартинок, а также свои картинки, а админы постараются заняться этим!\nДанный бот принимает текст, картинки, документы и стикеры.")
|
ForwardMessageCommand
|
||||||
|
])
|
||||||
@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Но они не смогут вам ответить из-за ваших настроек конфиденциальности.'))
|
|
||||||
@@ -7,4 +7,8 @@ class Singleton:
|
|||||||
def __new__(cls, *args, **kwargs) -> Self:
|
def __new__(cls, *args, **kwargs) -> Self:
|
||||||
if cls not in cls._instances:
|
if cls not in cls._instances:
|
||||||
cls._instances[cls] = super(Singleton, cls).__new__(cls)
|
cls._instances[cls] = super(Singleton, cls).__new__(cls)
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls: Self):
|
||||||
return cls._instances[cls]
|
return cls._instances[cls]
|
||||||
Reference in New Issue
Block a user