Deleted mysql support, redis password, webhook, keyboard constructors, filters, loguru.
Added support of decorators in handlers Added function to notify admins on startup
This commit is contained in:
parent
13ff8d0478
commit
eab45a5088
44
bot.py
44
bot.py
|
@ -1,42 +1,36 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.fsm_storage.redis import RedisStorage2
|
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
|
from data import config
|
||||||
|
|
||||||
bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML)
|
bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML)
|
||||||
storage = RedisStorage2(**config.aiogram_redis)
|
storage = RedisStorage2(**config.aiogram_redis)
|
||||||
dp = Dispatcher(bot, storage=storage)
|
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(dp):
|
||||||
async def on_startup(web_app: web.Application):
|
import filters
|
||||||
|
import middlewares
|
||||||
filters.setup(dp)
|
filters.setup(dp)
|
||||||
middlewares.setup(dp)
|
middlewares.setup(dp)
|
||||||
handlers.errors.setup(dp)
|
|
||||||
handlers.user.setup(dp)
|
from utils.notify_admins import on_startup_notify
|
||||||
await dp.bot.delete_webhook()
|
await on_startup_notify(dp)
|
||||||
await dp.bot.set_webhook(config.WEBHOOK_URL)
|
|
||||||
|
|
||||||
|
|
||||||
async def execute(req: web.Request) -> web.Response:
|
async def on_shutdown(dp):
|
||||||
upds = [types.Update(**(await req.json()))]
|
await bot.close()
|
||||||
Bot.set_current(dp.bot)
|
await storage.close()
|
||||||
Dispatcher.set_current(dp)
|
|
||||||
try:
|
|
||||||
await dp.process_updates(upds)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
finally:
|
|
||||||
return web.Response()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = web.Application()
|
from aiogram import executor
|
||||||
app.on_startup.append(on_startup)
|
from handlers import dp
|
||||||
app.add_routes([web.post(config.WEBHOOK_PATH, execute)])
|
|
||||||
web.run_app(app, port=5151, host='localhost')
|
executor.start_polling(dp, on_startup=on_startup)
|
||||||
|
|
|
@ -1,31 +1,20 @@
|
||||||
BOT_TOKEN = ''
|
import os
|
||||||
BASE_URL = 'https://example.com' # Webhook domain
|
|
||||||
WEBHOOK_PATH = f'/webhook/bot/{BOT_TOKEN}'
|
|
||||||
WEBHOOK_URL = f'{BASE_URL}{WEBHOOK_PATH}'
|
|
||||||
|
|
||||||
admins = []
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
ip = {
|
load_dotenv()
|
||||||
'db': '',
|
|
||||||
'redis': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
mysql_info = {
|
BOT_TOKEN = str(os.getenv("BOT_TOKEN"))
|
||||||
'host': ip['db'],
|
admins = [
|
||||||
'user': '',
|
]
|
||||||
'password': '',
|
|
||||||
'db': '',
|
ip = os.getenv("ip")
|
||||||
'maxsize': 5,
|
|
||||||
'port': 3306,
|
|
||||||
}
|
|
||||||
|
|
||||||
aiogram_redis = {
|
aiogram_redis = {
|
||||||
'host': ip['redis'],
|
'host': ip,
|
||||||
'password': ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
redis = {
|
redis = {
|
||||||
'address': (ip['redis'], 6379),
|
'address': (ip, 6379),
|
||||||
'password': '',
|
|
||||||
'encoding': 'utf8'
|
'encoding': 'utf8'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from aiogram import Dispatcher
|
from aiogram import Dispatcher
|
||||||
|
|
||||||
from .is_admin import AdminFilter
|
|
||||||
|
# from .is_admin import AdminFilter
|
||||||
|
|
||||||
|
|
||||||
def setup(dp: Dispatcher):
|
def setup(dp: Dispatcher):
|
||||||
dp.filters_factory.bind(AdminFilter)
|
# dp.filters_factory.bind(AdminFilter)
|
||||||
|
pass
|
||||||
|
|
|
@ -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
|
|
|
@ -1,2 +1,4 @@
|
||||||
from . import errors
|
from .errors import dp
|
||||||
from . import user
|
from .users import dp
|
||||||
|
|
||||||
|
__all__ = ["dp"]
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
from aiogram import Dispatcher
|
from .error_handler import dp
|
||||||
from aiogram.utils import exceptions
|
|
||||||
|
|
||||||
from .not_modified import message_not_modified, message_to_delete_not_found
|
__all__ = ["dp"]
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
56
handlers/errors/error_handler.py
Normal file
56
handlers/errors/error_handler.py
Normal file
|
@ -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}')
|
|
@ -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
|
|
0
handlers/groups/__init__.py
Normal file
0
handlers/groups/__init__.py
Normal file
|
@ -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())
|
|
|
@ -1,5 +0,0 @@
|
||||||
from aiogram import types
|
|
||||||
|
|
||||||
|
|
||||||
async def bot_start(msg: types.Message):
|
|
||||||
await msg.answer(f'Привет, {msg.from_user.full_name}!')
|
|
5
handlers/users/__init__.py
Normal file
5
handlers/users/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from .help import dp
|
||||||
|
from .start import dp
|
||||||
|
from .echo import dp
|
||||||
|
|
||||||
|
__all__ = ["dp"]
|
7
handlers/users/echo.py
Normal file
7
handlers/users/echo.py
Normal file
|
@ -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)
|
|
@ -1,13 +1,16 @@
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
|
from aiogram.dispatcher.filters.builtin import CommandStart
|
||||||
|
|
||||||
|
from bot import dp
|
||||||
from utils.misc import rate_limit
|
from utils.misc import rate_limit
|
||||||
|
|
||||||
|
|
||||||
@rate_limit(5, 'help')
|
@rate_limit(5, 'help')
|
||||||
async def bot_help(msg: types.Message):
|
@dp.message_handler(CommandStart())
|
||||||
|
async def bot_help(message: types.Message):
|
||||||
text = [
|
text = [
|
||||||
'Список команд: ',
|
'Список команд: ',
|
||||||
'/start - Начать диалог',
|
'/start - Начать диалог',
|
||||||
'/help - Получить справку'
|
'/help - Получить справку'
|
||||||
]
|
]
|
||||||
await msg.answer('\n'.join(text))
|
await message.answer('\n'.join(text))
|
9
handlers/users/start.py
Normal file
9
handlers/users/start.py
Normal file
|
@ -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}!')
|
|
@ -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
|
|
|
@ -1 +0,0 @@
|
||||||
from . import misc
|
|
|
@ -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
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from aiogram.utils.callback_data import CallbackData
|
||||||
|
|
||||||
|
some_callback = CallbackData("new")
|
|
@ -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
|
|
|
@ -1 +0,0 @@
|
||||||
from . import misc
|
|
|
@ -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
|
|
|
@ -4,4 +4,4 @@ from .throttling import ThrottlingMiddleware
|
||||||
|
|
||||||
|
|
||||||
def setup(dp: Dispatcher):
|
def setup(dp: Dispatcher):
|
||||||
dp.middleware.setup(ThrottlingMiddleware)
|
dp.middleware.setup(ThrottlingMiddleware())
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
aiomysql==0.0.20
|
|
||||||
aiogram==2.7
|
aiogram==2.7
|
||||||
aiohttp==3.6.2
|
aiohttp==3.6.2
|
||||||
aioredis==1.3.1
|
aioredis==1.3.1
|
||||||
loguru==0.4.1
|
|
|
@ -1 +0,0 @@
|
||||||
from . import user
|
|
|
@ -1,3 +1,4 @@
|
||||||
from . import db_api
|
from . import db_api
|
||||||
from . import misc
|
from . import misc
|
||||||
from . import redis
|
from . import redis
|
||||||
|
from .notify_admins import on_startup_notify
|
||||||
|
|
|
@ -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()
|
|
14
utils/notify_admins.py
Normal file
14
utils/notify_admins.py
Normal file
|
@ -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)
|
Loading…
Reference in New Issue
Block a user