From eab45a508881b1928079834ab5220e12ebb6bfec Mon Sep 17 00:00:00 2001 From: Kostiantyn Kriuchkov Date: Tue, 14 Apr 2020 01:50:57 +0300 Subject: [PATCH] Deleted mysql support, redis password, webhook, keyboard constructors, filters, loguru. Added support of decorators in handlers Added function to notify admins on startup --- bot.py | 44 +++++++-------- data/config.py | 31 ++++------ filters/__init__.py | 6 +- filters/is_admin.py | 14 ----- handlers/__init__.py | 6 +- .../user => handlers/channels}/__init__.py | 0 handlers/errors/__init__.py | 10 +--- handlers/errors/error_handler.py | 56 +++++++++++++++++++ handlers/errors/not_modified.py | 10 ---- handlers/groups/__init__.py | 0 handlers/user/__init__.py | 10 ---- handlers/user/start.py | 5 -- handlers/users/__init__.py | 5 ++ handlers/users/echo.py | 7 +++ handlers/{user => users}/help.py | 7 ++- handlers/users/start.py | 9 +++ keyboards/default/consts.py | 15 ----- keyboards/default/utils/__init__.py | 1 - keyboards/default/utils/misc.py | 17 ------ keyboards/inline/__init__.py | 3 + .../{callbacks.py => callback_datas.py} | 0 keyboards/inline/consts.py | 21 ------- keyboards/inline/utils/__init__.py | 1 - keyboards/inline/utils/misc.py | 17 ------ middlewares/__init__.py | 2 +- requirements.txt | 4 +- states/__init__.py | 1 - utils/__init__.py | 1 + utils/db_api/consts.py | 47 ---------------- utils/notify_admins.py | 14 +++++ 30 files changed, 141 insertions(+), 223 deletions(-) delete mode 100644 filters/is_admin.py rename {states/user => handlers/channels}/__init__.py (100%) create mode 100644 handlers/errors/error_handler.py delete mode 100644 handlers/errors/not_modified.py create mode 100644 handlers/groups/__init__.py delete mode 100644 handlers/user/__init__.py delete mode 100644 handlers/user/start.py create mode 100644 handlers/users/__init__.py create mode 100644 handlers/users/echo.py rename handlers/{user => users}/help.py (54%) create mode 100644 handlers/users/start.py delete mode 100644 keyboards/default/consts.py delete mode 100644 keyboards/default/utils/__init__.py delete mode 100644 keyboards/default/utils/misc.py rename keyboards/inline/{callbacks.py => callback_datas.py} (100%) delete mode 100644 keyboards/inline/consts.py delete mode 100644 keyboards/inline/utils/__init__.py delete mode 100644 keyboards/inline/utils/misc.py delete mode 100644 utils/db_api/consts.py create mode 100644 utils/notify_admins.py diff --git a/bot.py b/bot.py index 59f6cc3..393d96c 100644 --- a/bot.py +++ b/bot.py @@ -1,42 +1,36 @@ +import logging + from aiogram import Bot, Dispatcher, types from aiogram.contrib.fsm_storage.redis import RedisStorage2 -from aiohttp import web -from loguru import logger -import filters -import handlers -import middlewares from data import config bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML) storage = RedisStorage2(**config.aiogram_redis) dp = Dispatcher(bot, storage=storage) +logging.basicConfig(format=u'%(filename)s [LINE:%(lineno)d] #%(levelname)-8s [%(asctime)s] %(message)s', + level=logging.INFO, + # level=logging.DEBUG, + ) -# noinspection PyUnusedLocal -async def on_startup(web_app: web.Application): +async def on_startup(dp): + import filters + import middlewares filters.setup(dp) middlewares.setup(dp) - handlers.errors.setup(dp) - handlers.user.setup(dp) - await dp.bot.delete_webhook() - await dp.bot.set_webhook(config.WEBHOOK_URL) + + from utils.notify_admins import on_startup_notify + await on_startup_notify(dp) -async def execute(req: web.Request) -> web.Response: - upds = [types.Update(**(await req.json()))] - Bot.set_current(dp.bot) - Dispatcher.set_current(dp) - try: - await dp.process_updates(upds) - except Exception as e: - logger.error(e) - finally: - return web.Response() +async def on_shutdown(dp): + await bot.close() + await storage.close() if __name__ == '__main__': - app = web.Application() - app.on_startup.append(on_startup) - app.add_routes([web.post(config.WEBHOOK_PATH, execute)]) - web.run_app(app, port=5151, host='localhost') + from aiogram import executor + from handlers import dp + + executor.start_polling(dp, on_startup=on_startup) diff --git a/data/config.py b/data/config.py index 7555a33..5bce25d 100644 --- a/data/config.py +++ b/data/config.py @@ -1,31 +1,20 @@ -BOT_TOKEN = '' -BASE_URL = 'https://example.com' # Webhook domain -WEBHOOK_PATH = f'/webhook/bot/{BOT_TOKEN}' -WEBHOOK_URL = f'{BASE_URL}{WEBHOOK_PATH}' +import os -admins = [] +from dotenv import load_dotenv -ip = { - 'db': '', - 'redis': '', -} +load_dotenv() -mysql_info = { - 'host': ip['db'], - 'user': '', - 'password': '', - 'db': '', - 'maxsize': 5, - 'port': 3306, -} +BOT_TOKEN = str(os.getenv("BOT_TOKEN")) +admins = [ +] + +ip = os.getenv("ip") aiogram_redis = { - 'host': ip['redis'], - 'password': '' + 'host': ip, } redis = { - 'address': (ip['redis'], 6379), - 'password': '', + 'address': (ip, 6379), 'encoding': 'utf8' } diff --git a/filters/__init__.py b/filters/__init__.py index 717448b..2ba789c 100644 --- a/filters/__init__.py +++ b/filters/__init__.py @@ -1,7 +1,9 @@ from aiogram import Dispatcher -from .is_admin import AdminFilter + +# from .is_admin import AdminFilter def setup(dp: Dispatcher): - dp.filters_factory.bind(AdminFilter) + # dp.filters_factory.bind(AdminFilter) + pass diff --git a/filters/is_admin.py b/filters/is_admin.py deleted file mode 100644 index 9dcdce8..0000000 --- a/filters/is_admin.py +++ /dev/null @@ -1,14 +0,0 @@ -from aiogram import types -from aiogram.dispatcher.filters import BoundFilter - -from data import config - - -class AdminFilter(BoundFilter): - key = 'is_admin' - - def __init__(self, is_admin): - self.is_admin = is_admin - - async def check(self, message: types.Message): - return message.from_user.id in config.admins diff --git a/handlers/__init__.py b/handlers/__init__.py index f708952..12a53ba 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -1,2 +1,4 @@ -from . import errors -from . import user +from .errors import dp +from .users import dp + +__all__ = ["dp"] diff --git a/states/user/__init__.py b/handlers/channels/__init__.py similarity index 100% rename from states/user/__init__.py rename to handlers/channels/__init__.py diff --git a/handlers/errors/__init__.py b/handlers/errors/__init__.py index 614e0e6..4a40b9b 100644 --- a/handlers/errors/__init__.py +++ b/handlers/errors/__init__.py @@ -1,9 +1,3 @@ -from aiogram import Dispatcher -from aiogram.utils import exceptions +from .error_handler import dp -from .not_modified import message_not_modified, message_to_delete_not_found - - -def setup(dp: Dispatcher): - dp.register_errors_handler(message_not_modified, exception=exceptions.MessageNotModified) - dp.register_errors_handler(message_to_delete_not_found, exception=exceptions.MessageToDeleteNotFound) +__all__ = ["dp"] diff --git a/handlers/errors/error_handler.py b/handlers/errors/error_handler.py new file mode 100644 index 0000000..c361bc1 --- /dev/null +++ b/handlers/errors/error_handler.py @@ -0,0 +1,56 @@ +import logging + +from bot import dp + + +@dp.errors_handler() +async def errors_handler(update, exception): + """ + Exceptions handler. Catches all exceptions within task factory tasks. + :param dispatcher: + :param update: + :param exception: + :return: stdout logging + """ + from aiogram.utils.exceptions import (Unauthorized, InvalidQueryID, TelegramAPIError, + CantDemoteChatCreator, MessageNotModified, MessageToDeleteNotFound, + MessageTextIsEmpty, RetryAfter, + CantParseEntities, MessageCantBeDeleted) + + if isinstance(exception, CantDemoteChatCreator): + logging.debug("Can't demote chat creator") + return True + + if isinstance(exception, MessageNotModified): + logging.debug('Message is not modified') + return True + if isinstance(exception, MessageCantBeDeleted): + logging.debug('Message cant be deleted') + return True + + if isinstance(exception, MessageToDeleteNotFound): + logging.debug('Message to delete not found') + return True + + if isinstance(exception, MessageTextIsEmpty): + logging.debug('MessageTextIsEmpty') + return True + + if isinstance(exception, Unauthorized): + logging.info(f'Unauthorized: {exception}') + return True + + if isinstance(exception, InvalidQueryID): + logging.exception(f'InvalidQueryID: {exception} \nUpdate: {update}') + return True + + if isinstance(exception, TelegramAPIError): + logging.exception(f'TelegramAPIError: {exception} \nUpdate: {update}') + return True + if isinstance(exception, RetryAfter): + logging.exception(f'RetryAfter: {exception} \nUpdate: {update}') + return True + if isinstance(exception, CantParseEntities): + logging.exception(f'CantParseEntities: {exception} \nUpdate: {update}') + return True + logging.exception(f'Update: {update} \n{exception}') diff --git a/handlers/errors/not_modified.py b/handlers/errors/not_modified.py deleted file mode 100644 index acfd50e..0000000 --- a/handlers/errors/not_modified.py +++ /dev/null @@ -1,10 +0,0 @@ -from aiogram import types -from aiogram.utils import exceptions - - -async def message_not_modified(update: types.Update, error: exceptions.MessageNotModified): - return True - - -async def message_to_delete_not_found(update: types.Update, error: exceptions.MessageToDeleteNotFound): - return True diff --git a/handlers/groups/__init__.py b/handlers/groups/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/handlers/user/__init__.py b/handlers/user/__init__.py deleted file mode 100644 index 037e614..0000000 --- a/handlers/user/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from aiogram import Dispatcher -from aiogram.dispatcher.filters import CommandStart, CommandHelp - -from .help import bot_help -from .start import bot_start - - -def setup(dp: Dispatcher): - dp.register_message_handler(bot_start, CommandStart()) - dp.register_message_handler(bot_help, CommandHelp()) diff --git a/handlers/user/start.py b/handlers/user/start.py deleted file mode 100644 index a35def6..0000000 --- a/handlers/user/start.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram import types - - -async def bot_start(msg: types.Message): - await msg.answer(f'Привет, {msg.from_user.full_name}!') diff --git a/handlers/users/__init__.py b/handlers/users/__init__.py new file mode 100644 index 0000000..4f3c9f7 --- /dev/null +++ b/handlers/users/__init__.py @@ -0,0 +1,5 @@ +from .help import dp +from .start import dp +from .echo import dp + +__all__ = ["dp"] diff --git a/handlers/users/echo.py b/handlers/users/echo.py new file mode 100644 index 0000000..b147304 --- /dev/null +++ b/handlers/users/echo.py @@ -0,0 +1,7 @@ +from aiogram import types +from bot import dp + + +@dp.message_handler() +async def bot_start(message: types.Message): + await message.answer(message.text) diff --git a/handlers/user/help.py b/handlers/users/help.py similarity index 54% rename from handlers/user/help.py rename to handlers/users/help.py index 105fb38..19e739a 100644 --- a/handlers/user/help.py +++ b/handlers/users/help.py @@ -1,13 +1,16 @@ from aiogram import types +from aiogram.dispatcher.filters.builtin import CommandStart +from bot import dp from utils.misc import rate_limit @rate_limit(5, 'help') -async def bot_help(msg: types.Message): +@dp.message_handler(CommandStart()) +async def bot_help(message: types.Message): text = [ 'Список команд: ', '/start - Начать диалог', '/help - Получить справку' ] - await msg.answer('\n'.join(text)) + await message.answer('\n'.join(text)) diff --git a/handlers/users/start.py b/handlers/users/start.py new file mode 100644 index 0000000..922113d --- /dev/null +++ b/handlers/users/start.py @@ -0,0 +1,9 @@ +from aiogram import types +from aiogram.dispatcher.filters.builtin import CommandStart + +from bot import dp + + +@dp.message_handler(CommandStart()) +async def bot_start(message: types.Message): + await message.answer(f'Привет, {message.from_user.full_name}!') diff --git a/keyboards/default/consts.py b/keyboards/default/consts.py deleted file mode 100644 index 3203ff2..0000000 --- a/keyboards/default/consts.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import List - -from aiogram.types import ReplyKeyboardMarkup, KeyboardButton - -from . import utils - - -class DefaultConstructor: - @staticmethod - def _create_kb(actions: List[str], schema: List[int]) -> ReplyKeyboardMarkup: - btns = [] - for a in actions: - btns.append(KeyboardButton(a)) - kb = utils.misc.arrange_default_schema(btns, schema) - return kb diff --git a/keyboards/default/utils/__init__.py b/keyboards/default/utils/__init__.py deleted file mode 100644 index ab665e1..0000000 --- a/keyboards/default/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import misc diff --git a/keyboards/default/utils/misc.py b/keyboards/default/utils/misc.py deleted file mode 100644 index a02b484..0000000 --- a/keyboards/default/utils/misc.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import List - -from aiogram.types import ReplyKeyboardMarkup, KeyboardButton - - -def arrange_default_schema(buttons: List[KeyboardButton], count: List[int]) -> ReplyKeyboardMarkup: - kb = ReplyKeyboardMarkup(resize_keyboard=True) - kb.row_width = max(count) - if sum(count) != len(buttons): - raise ValueError('Количество кнопок не совпадает со схемой') - tmplist = [] - for a in count: - tmplist.append([]) - for _ in range(a): - tmplist[-1].append(buttons.pop(0)) - kb.keyboard = tmplist - return kb diff --git a/keyboards/inline/__init__.py b/keyboards/inline/__init__.py index e69de29..934f94d 100644 --- a/keyboards/inline/__init__.py +++ b/keyboards/inline/__init__.py @@ -0,0 +1,3 @@ +from aiogram.utils.callback_data import CallbackData + +some_callback = CallbackData("new") diff --git a/keyboards/inline/callbacks.py b/keyboards/inline/callback_datas.py similarity index 100% rename from keyboards/inline/callbacks.py rename to keyboards/inline/callback_datas.py diff --git a/keyboards/inline/consts.py b/keyboards/inline/consts.py deleted file mode 100644 index 6f5e663..0000000 --- a/keyboards/inline/consts.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import List, Tuple, Dict - -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from aiogram.utils.callback_data import CallbackData - -from . import utils - - -class InlineConstructor: - @staticmethod - def _create_kb(actions: List[Tuple[str, Dict[str, str], CallbackData]], schema: List[int]) -> InlineKeyboardMarkup: - btns = [] - for a, b, c in actions: - btns.append( - InlineKeyboardButton( - text=a, - callback_data=c.new(**b) - ) - ) - kb = utils.misc.arrange_inline_schema(btns, schema) - return kb diff --git a/keyboards/inline/utils/__init__.py b/keyboards/inline/utils/__init__.py deleted file mode 100644 index ab665e1..0000000 --- a/keyboards/inline/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import misc diff --git a/keyboards/inline/utils/misc.py b/keyboards/inline/utils/misc.py deleted file mode 100644 index 7366f2e..0000000 --- a/keyboards/inline/utils/misc.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import List - -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup - - -def arrange_inline_schema(buttons: List[InlineKeyboardButton], count: List[int]) -> InlineKeyboardMarkup: - kb = InlineKeyboardMarkup() - kb.row_width = max(count) - if sum(count) != len(buttons): - raise ValueError('Количество кнопок не совпадает со схемой') - tmplist = [] - for a in count: - tmplist.append([]) - for _ in range(a): - tmplist[-1].append(buttons.pop(0)) - kb.inline_keyboard = tmplist - return kb diff --git a/middlewares/__init__.py b/middlewares/__init__.py index 1ea7546..030694e 100644 --- a/middlewares/__init__.py +++ b/middlewares/__init__.py @@ -4,4 +4,4 @@ from .throttling import ThrottlingMiddleware def setup(dp: Dispatcher): - dp.middleware.setup(ThrottlingMiddleware) + dp.middleware.setup(ThrottlingMiddleware()) diff --git a/requirements.txt b/requirements.txt index 510d243..7613d94 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -aiomysql==0.0.20 aiogram==2.7 aiohttp==3.6.2 -aioredis==1.3.1 -loguru==0.4.1 +aioredis==1.3.1 \ No newline at end of file diff --git a/states/__init__.py b/states/__init__.py index f9b61db..e69de29 100644 --- a/states/__init__.py +++ b/states/__init__.py @@ -1 +0,0 @@ -from . import user diff --git a/utils/__init__.py b/utils/__init__.py index 976134d..997f1b6 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,3 +1,4 @@ from . import db_api from . import misc from . import redis +from .notify_admins import on_startup_notify diff --git a/utils/db_api/consts.py b/utils/db_api/consts.py deleted file mode 100644 index 4210608..0000000 --- a/utils/db_api/consts.py +++ /dev/null @@ -1,47 +0,0 @@ -import asyncio -from typing import Optional, Dict, Any, Union, List - -import aiomysql - -from data import config - - -class RawConnection: - connection_pool = None - - @staticmethod - async def _make_request( - sql: str, - params: Union[tuple, List[tuple]] = None, - fetch: bool = False, - mult: bool = False, - retries_count: int = 5 - ) -> Optional[Union[List[Dict[str, Any]], Dict[str, Any]]]: - if RawConnection.connection_pool is None: - RawConnection.connection_pool = await aiomysql.create_pool(**config.mysql_info) - async with RawConnection.connection_pool.acquire() as conn: - conn: aiomysql.Connection = conn - async with conn.cursor(aiomysql.DictCursor) as cur: - cur: aiomysql.DictCursor = cur - for i in range(retries_count): - try: - if isinstance(params, list): - await cur.executemany(sql, params) - else: - await cur.execute(sql, params) - except aiomysql.OperationalError as e: - if 'Deadlock found' in str(e): - await asyncio.sleep(1) - except aiomysql.InternalError as e: - if 'Deadlock found' in str(e): - await asyncio.sleep(1) - else: - break - if fetch: - if mult: - r = await cur.fetchall() - else: - r = await cur.fetchone() - return r - else: - await conn.commit() diff --git a/utils/notify_admins.py b/utils/notify_admins.py new file mode 100644 index 0000000..c920196 --- /dev/null +++ b/utils/notify_admins.py @@ -0,0 +1,14 @@ +import logging + +from aiogram import Dispatcher + +from data.config import admins + + +async def on_startup_notify(dp: Dispatcher): + for admin in admins: + try: + await dp.bot.send_message(admin, "Бот Запущен") + + except Exception as err: + logging.exception(err)