From a8487334226ca69591e3b1d295fd811fdcfb12ab Mon Sep 17 00:00:00 2001 From: Sergey Elpashev Date: Mon, 27 Nov 2023 22:04:50 +0300 Subject: [PATCH] Some refactoring and started second bot --- .env.example | 2 + handlers/admin_commands.py | 18 +++------ handlers/handler.py | 16 ++++++++ handlers/user_commands.py | 21 +++------- main.py | 75 +++++++++++++++++++++++++----------- neuroapi/config.py | 24 ++++++------ neuroapi/types/__init__.py | 5 ++- neuroapi/types/_singleton.py | 10 +++++ 8 files changed, 108 insertions(+), 63 deletions(-) create mode 100644 handlers/handler.py create mode 100644 neuroapi/types/_singleton.py diff --git a/.env.example b/.env.example index 317fe12..ae0343f 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ TOKEN= +PROXY_TOKEN= + API_URL="http://localhost:3000" \ No newline at end of file diff --git a/handlers/admin_commands.py b/handlers/admin_commands.py index 81a462b..2641225 100644 --- a/handlers/admin_commands.py +++ b/handlers/admin_commands.py @@ -1,6 +1,6 @@ -from typing import Any, List +from typing import List -from aiogram import Bot, F, Router, types +from aiogram import Bot, F, types from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.utils.media_group import MediaGroupBuilder @@ -9,6 +9,7 @@ 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.middlewares.user import AdminMiddleware from handlers.states.change_post import ChangePost from neuroapi import neuroapi @@ -23,13 +24,9 @@ def get_post_info(post: neuroTypes.Post, post_id: int) -> str: return s -class Admin_commands: - bot: Bot - router: Router - +class AdminCommands(Handler): def __init__(self, bot: Bot) -> None: - self.bot = bot - self.router = Router() + super().__init__(bot) self.router.message.middleware(AdminMiddleware()) @self.router.message(NewPostFilter()) @@ -210,9 +207,4 @@ class Admin_commands: except Exception as e: print(e) - def __call__(self, *args: Any, **kwds: Any) -> Router: - return self.router - -def setup(bot: Bot) -> Router: - return Admin_commands(bot)() diff --git a/handlers/handler.py b/handlers/handler.py new file mode 100644 index 0000000..ff4548c --- /dev/null +++ b/handlers/handler.py @@ -0,0 +1,16 @@ +from typing import Any + +from aiogram import Bot, Router + + +class Handler: + bot: Bot + router: Router + + def __init__(self, bot: Bot) -> None: + assert isinstance(bot, Bot) + self.bot = bot + self.router = Router() + + def __call__(self) -> Router: + return self.router \ No newline at end of file diff --git a/handlers/user_commands.py b/handlers/user_commands.py index 2cf48cc..4c94102 100644 --- a/handlers/user_commands.py +++ b/handlers/user_commands.py @@ -1,20 +1,17 @@ -from typing import Any, List +from typing import List -from aiogram import Bot, F, Router, types +from aiogram import Bot, F, types +from handlers.handler import Handler from neuroapi import neuroapi - from neuroapi.types import Admin as AdminType -class User_commands: - bot: Bot - router: Router +class UserCommands(Handler): def __init__(self, bot: Bot) -> None: - self.bot = bot - self.router = Router() - + super().__init__(bot) + @self.router.message(F.chat.type == 'private') async def forward_post(message: types.Message): admins: List[AdminType] = await neuroapi.admin.get() @@ -27,9 +24,3 @@ class User_commands: canReply = False await message.reply('Ваше сообщение было отправлено администраторам'+('' if canReply else '\nНо они не смогут вам ответить из-за ваших настроек конфиденциальности.')) - def __call__(self, *args: Any, **kwds: Any) -> Router: - return self.router - - -def setup(bot: Bot) -> Router: - return User_commands(bot)() diff --git a/main.py b/main.py index 2cff85e..5795646 100644 --- a/main.py +++ b/main.py @@ -1,37 +1,68 @@ import asyncio import logging -import os +import signal import sys -from os.path import dirname, join # import aioschedule as schedule -import dotenv -from aiogram import Bot, Dispatcher, types -from aiogram.filters import CommandStart +from aiogram import Bot, Dispatcher -dotenv.load_dotenv() +from handlers.admin_commands import AdminCommands +from handlers.handler import Handler +from handlers.user_commands import UserCommands +from neuroapi.config import Config -token = os.getenv('TOKEN') -bot = Bot(token) -dp = Dispatcher() +class NeuroApiBot: + bot: Bot + dp: Dispatcher + + _instances = {} + + def __init__(self, token: str) -> None: + self.bot = Bot(token) + self.dp = Dispatcher() + self._instances + + def __new__(cls, token: str) -> 'NeuroApiBot': + assert isinstance(token, str) + if token not in cls._instances: + cls._instances[token] = super(NeuroApiBot, cls).__new__(cls) + return cls._instances[token] + + def include_router(self, *routerClasses: Handler) -> None: + for routerClass in routerClasses: + assert issubclass(routerClass, Handler) + self.dp.include_routers(routerClass(self.bot)()) + + async def start(self, skip_updates=True): + await self.dp.start_polling(self.bot, skip_updates=skip_updates) -@dp.message(CommandStart()) -async def start_message(message: types.Message): - await message.answer('Добро пожаловать в бота ') - -handlers_dir = join(dirname(__file__), 'handlers') - -for filename in os.listdir(handlers_dir): - if filename.endswith('.py'): - module_name = filename[:-3] - setup = __import__(f"handlers.{module_name}", locals(), globals(), ['setup']).setup - dp.include_router(setup(bot)) +async def delay_bot()->None: + if Config().token is None: + print('Delay bot needs token in environment') + return + bot = NeuroApiBot(Config().token) + bot.include_router(AdminCommands, UserCommands) + await bot.start() +async def proxy_bot()->None: + if Config().proxy_token is None: + print('Proxy bot needs token in environment') + return + bot = NeuroApiBot(Config().proxy_token) + bot.include_router() + await bot.start() async def main() -> None: - await dp.start_polling(bot, skip_updates=True) + tasks = [asyncio.create_task(delay_bot()), asyncio.create_task(proxy_bot())] + await asyncio.gather(*tasks) if __name__ == '__main__': logging.basicConfig(level=logging.INFO, stream=sys.stdout) - asyncio.run(main()) \ No newline at end of file + loop = asyncio.get_event_loop() + for signame in ('SIGINT', 'SIGTERM'): + loop.add_signal_handler(getattr(signal, signame), loop.stop) + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass \ No newline at end of file diff --git a/neuroapi/config.py b/neuroapi/config.py index 9dd571d..3d82414 100644 --- a/neuroapi/config.py +++ b/neuroapi/config.py @@ -1,21 +1,15 @@ import os import tomllib -from typing import List, Self, TypeVar +from typing import List, Optional from attr import dataclass from dotenv import load_dotenv +from neuroapi.types import Singleton + from .types._helpers import * -class _Singleton: - _instances = {} - - def __new__(cls) -> Self: - if cls not in cls._instances: - cls._instances[cls] = super(_Singleton, cls).__new__(cls) - return cls._instances[cls] - @dataclass class Settings: time: List[str] @@ -31,13 +25,21 @@ class Settings: result['time'] = from_list(from_str, self.time) return result -class Config(_Singleton): +class Config(Singleton): api_url: str settings: Settings + token: Optional[str] + proxy_token: Optional[str] def __init__(self): load_dotenv(os.path.join(os.path.dirname(__file__), '..', '.env')) if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'settings.toml')): raise Exception('Settings.toml must be in root folder') with open(os.path.join(os.path.dirname(__file__), '..', 'settings.toml'), 'rb') as f: settings = tomllib.load(f) self.settings = Settings.from_dict(settings) - self.api_url = os.environ.get('API_URL') \ No newline at end of file + self.api_url = os.environ.get('API_URL') + self.token = os.environ.get('TOKEN') + if self.token == '': + self.token = None + self.proxy_token = os.environ.get('PROXY_TOKEN') + if self.proxy_token == '': + self.proxy_token = None \ No newline at end of file diff --git a/neuroapi/types/__init__.py b/neuroapi/types/__init__.py index cb41a50..bfa4e2f 100644 --- a/neuroapi/types/__init__.py +++ b/neuroapi/types/__init__.py @@ -1,4 +1,5 @@ -from ._post import Post -from ._image import Image from ._admin import Admin +from ._image import Image +from ._post import Post +from ._singleton import Singleton from ._user import User diff --git a/neuroapi/types/_singleton.py b/neuroapi/types/_singleton.py new file mode 100644 index 0000000..46d6a87 --- /dev/null +++ b/neuroapi/types/_singleton.py @@ -0,0 +1,10 @@ +from typing import Self + + +class Singleton: + _instances = {} + + def __new__(cls, *args, **kwargs) -> Self: + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__new__(cls) + return cls._instances[cls] \ No newline at end of file