diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..97933f0 --- /dev/null +++ b/bot.py @@ -0,0 +1,39 @@ +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 +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) + + +# noinspection PyUnusedLocal +async def on_startup(web_app: web.Application): + filters.setup(dp) + handlers.user.setup(dp) + await dp.bot.delete_webhook() + await dp.bot.set_webhook(config.WEBHOOK_URL) + + +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() + + +if __name__ == '__main__': + app = web.Application() + app.on_startup.append(on_startup) + app.add_routes([web.post('/webhook/{token}', execute)]) + web.run_app(app, port=5151, host='localhost') diff --git a/data/__init__.py b/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/data/config.py b/data/config.py new file mode 100644 index 0000000..bd4c5c7 --- /dev/null +++ b/data/config.py @@ -0,0 +1,29 @@ +BOT_TOKEN = '' +WEBHOOK_URL = '' + +admins = [] + +ip = { + 'db': '', + 'redis': '', +} + +mysql_info = { + 'host': ip['db'], + 'user': '', + 'password': '', + 'db': '', + 'maxsize': 5, + 'port': 3306, +} + +aiogram_redis = { + 'host': ip['redis'], + 'password': '' +} + +redis = { + 'address': (ip['redis'], 6379), + 'password': '', + 'encoding': 'utf8' +} diff --git a/filters/__init__.py b/filters/__init__.py new file mode 100644 index 0000000..717448b --- /dev/null +++ b/filters/__init__.py @@ -0,0 +1,7 @@ +from aiogram import Dispatcher + +from .is_admin import AdminFilter + + +def setup(dp: Dispatcher): + dp.filters_factory.bind(AdminFilter) diff --git a/filters/is_admin.py b/filters/is_admin.py new file mode 100644 index 0000000..9dcdce8 --- /dev/null +++ b/filters/is_admin.py @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..f9b61db --- /dev/null +++ b/handlers/__init__.py @@ -0,0 +1 @@ +from . import user diff --git a/handlers/user/__init__.py b/handlers/user/__init__.py new file mode 100644 index 0000000..7e7c090 --- /dev/null +++ b/handlers/user/__init__.py @@ -0,0 +1,8 @@ +from aiogram import Dispatcher +from aiogram.dispatcher.filters import CommandStart + +from .start import bot_start + + +def setup(dp: Dispatcher): + dp.register_message_handler(start, CommandStart()) diff --git a/handlers/user/start.py b/handlers/user/start.py new file mode 100644 index 0000000..a35def6 --- /dev/null +++ b/handlers/user/start.py @@ -0,0 +1,5 @@ +from aiogram import types + + +async def bot_start(msg: types.Message): + await msg.answer(f'Привет, {msg.from_user.full_name}!') diff --git a/keyboards/__init__.py b/keyboards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/keyboards/default/__init__.py b/keyboards/default/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/keyboards/inline/__init__.py b/keyboards/inline/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/keyboards/inline/callbacks.py b/keyboards/inline/callbacks.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/keyboards/inline/callbacks.py @@ -0,0 +1 @@ + diff --git a/keyboards/inline/consts.py b/keyboards/inline/consts.py new file mode 100644 index 0000000..6f5e663 --- /dev/null +++ b/keyboards/inline/consts.py @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000..ab665e1 --- /dev/null +++ b/keyboards/inline/utils/__init__.py @@ -0,0 +1 @@ +from . import misc diff --git a/keyboards/inline/utils/misc.py b/keyboards/inline/utils/misc.py new file mode 100644 index 0000000..2e3379c --- /dev/null +++ b/keyboards/inline/utils/misc.py @@ -0,0 +1,17 @@ +from typing import List + +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup + + +def arrange_inline_schema(buttons: List[InlineKeyboardButton], count: List[int]) -> InlineKeyboardMarkup: + kb = InlineKeyboardMarkup() + btns = buttons + kb.row_width = max(count) + if sum(count) != len(buttons): + raise ValueError('Количество кнопок не совпадает со схемой') + tmplist = [[InlineKeyboardButton('') for _ in range(count[i])] for i in range(len(count))] + for a in range(len(tmplist)): + for b in range(len(tmplist[a])): + tmplist[a][b] = btns.pop(0) + kb.inline_keyboard = tmplist + return kb diff --git a/states/__init__.py b/states/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/states/user/__init__.py b/states/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/db_api/__init__.py b/utils/db_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/db_api/consts.py b/utils/db_api/consts.py new file mode 100644 index 0000000..73816db --- /dev/null +++ b/utils/db_api/consts.py @@ -0,0 +1,55 @@ +import asyncio +from typing import Optional, Dict, Any, Union, List + +import aiomysql + +from data import config + +connection_pool = None +mainloop = asyncio.get_event_loop() + + +async def main(loop): + global connection_pool + connection_pool = await aiomysql.create_pool(**config.mysql_info, loop=loop) + + +class RawConnection: + @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]]]: + global connection_pool + async with 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() + + +mainloop.run_until_complete(main(mainloop)) diff --git a/utils/redis/__init__.py b/utils/redis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/redis/consts.py b/utils/redis/consts.py new file mode 100644 index 0000000..dd7c801 --- /dev/null +++ b/utils/redis/consts.py @@ -0,0 +1,16 @@ +import asyncio +from typing import Optional + +import aioredis + +from data import config + +data_pool: Optional[aioredis.Redis] = None + + +async def create_pools(): + global data_pool + data_pool = await aioredis.create_redis_pool(**config.redis, db=1) + + +asyncio.get_event_loop().run_until_complete(create_pools())