diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..136ff40 --- /dev/null +++ b/.env.dist @@ -0,0 +1,14 @@ + +# Telegram API Token +BOT_TOKEN= + +# Не надо использовать пробел после "," +ADMINS_ID=123456789,987654321 + +# Указываем пропускать ли боту апдейты при запуске +SKIP_UPDATES=False + +# Параметры для Redis +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 + diff --git a/.gitignore b/.gitignore index 668c254..7ed07d0 100644 --- a/.gitignore +++ b/.gitignore @@ -127,14 +127,4 @@ dmypy.json # Pyre type checker .pyre/ -.idea/$CACHE_FILE$ -.idea/.gitignore -.idea/aiogram-bot-template.iml -.idea/codeStyles/ -.idea/deployment.xml -.idea/dictionaries -.idea/inspectionProfiles/ -.idea/misc.xml -.idea/modules.xml -.idea/vagrant.xml -.idea/vcs.xml +.idea/ diff --git a/app.py b/app.py index f1293d1..4d2b46a 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,5 @@ from loader import bot, storage +from data.config import SKIP_UPDATES async def on_startup(dp): @@ -8,7 +9,9 @@ async def on_startup(dp): middlewares.setup(dp) from utils.notify_admins import on_startup_notify + from utils.set_bot_commands import set_default_commands await on_startup_notify(dp) + await set_default_commands(dp) async def on_shutdown(dp): @@ -20,4 +23,4 @@ if __name__ == '__main__': from aiogram import executor from handlers import dp - executor.start_polling(dp, on_startup=on_startup, on_shutdown=on_shutdown) + executor.start_polling(dp, on_startup=on_startup, on_shutdown=on_shutdown, skip_updates=SKIP_UPDATES) diff --git a/data/config.py b/data/config.py index 5bce25d..c47b8cd 100644 --- a/data/config.py +++ b/data/config.py @@ -1,20 +1,22 @@ -import os +from environs import Env -from dotenv import load_dotenv +env = Env() +env.read_env() -load_dotenv() +BOT_TOKEN = env("BOT_TOKEN") -BOT_TOKEN = str(os.getenv("BOT_TOKEN")) -admins = [ -] +SKIP_UPDATES = env.bool("SKIP_UPDATES", False) +JOIN_NO_MEDIA_TIME = env.int("JOIN_NO_MEDIA_TIME", 10) +ADMINS_ID = env.list("ADMINS_ID") -ip = os.getenv("ip") +REDIS_HOST = env("REDIS_HOST", "127.0.0.1") +REDIS_PORT = env("REDIS_PORT", 6379) aiogram_redis = { - 'host': ip, + 'host': REDIS_HOST, } redis = { - 'address': (ip, 6379), + 'address': (REDIS_HOST, REDIS_PORT), 'encoding': 'utf8' } diff --git a/data/permissions.py b/data/permissions.py new file mode 100644 index 0000000..7bdc109 --- /dev/null +++ b/data/permissions.py @@ -0,0 +1,37 @@ +from aiogram import types + +# Права пользователя, только вошедшего в чат +new_user_added = types.ChatPermissions( + can_send_messages=False, + can_send_media_messages=False, + can_send_polls=False, + can_send_other_messages=False, + can_add_web_page_previews=False, + can_invite_users=False, + can_change_info=False, + can_pin_messages=False, +) + +# Права пользователя, подтвердившего, что он не бот +user_allowed = types.ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_polls=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + can_invite_users=True, + can_change_info=False, + can_pin_messages=False, +) + +# Права пользователя в муте +user_ro = types.ChatPermissions( + can_send_messages=False, + can_send_media_messages=False, + can_send_polls=False, + can_send_other_messages=False, + can_add_web_page_previews=False, + can_invite_users=True, + can_change_info=False, + can_pin_messages=False, +) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9231779 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.1' +services: + commonmoder: + container_name: commonmoder + build: + context: . + network_mode: "host" + + command: python app.py + restart: always + + env_file: + - ".env" diff --git a/filters/__init__.py b/filters/__init__.py index 2ba789c..331fdff 100644 --- a/filters/__init__.py +++ b/filters/__init__.py @@ -1,9 +1,10 @@ from aiogram import Dispatcher - -# from .is_admin import AdminFilter +from .user_filters import IsContributor +from .chat_filters import IsGroup, IsPrivate def setup(dp: Dispatcher): - # dp.filters_factory.bind(AdminFilter) - pass + dp.filters_factory.bind(IsContributor) + dp.filters_factory.bind(IsGroup) + dp.filters_factory.bind(IsPrivate) diff --git a/filters/chat_filters.py b/filters/chat_filters.py new file mode 100644 index 0000000..6f6facf --- /dev/null +++ b/filters/chat_filters.py @@ -0,0 +1,15 @@ +from aiogram import types +from aiogram.dispatcher.filters import BoundFilter + + +class IsGroup(BoundFilter): + + async def check(self, message: types.Message): + return message.chat.type in (types.ChatType.GROUP, + types.ChatType.SUPER_GROUP) + + +class IsPrivate(BoundFilter): + + async def check(self, message: types.Message): + return message.chat.type == types.ChatType.PRIVATE diff --git a/filters/user_filters.py b/filters/user_filters.py new file mode 100644 index 0000000..448aec0 --- /dev/null +++ b/filters/user_filters.py @@ -0,0 +1,11 @@ +from aiogram import types +from aiogram.dispatcher.filters import BoundFilter + +from loader import bot + + +class IsContributor(BoundFilter): + + async def check(self, message: types.Message): + member = await bot.get_chat_member(message.chat.id, message.from_user.id) + return member.custom_title == "Contributor" diff --git a/handlers/__init__.py b/handlers/__init__.py index d8db2bb..95e1691 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -1,6 +1,7 @@ -from .errors import dp -from .users import dp -from .groups import dp -from .channels import dp +# from .channels import dp +from .essential import dp +# from .groups import dp +# from .private import dp +# from .users import dp __all__ = ["dp"] diff --git a/handlers/channels/__init__.py b/handlers/channels/__init__.py index e69de29..22747cb 100644 --- a/handlers/channels/__init__.py +++ b/handlers/channels/__init__.py @@ -0,0 +1 @@ +# __all__ = ["dp"] diff --git a/handlers/errors/__init__.py b/handlers/errors/__init__.py deleted file mode 100644 index 4a40b9b..0000000 --- a/handlers/errors/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .error_handler import dp - -__all__ = ["dp"] diff --git a/handlers/essential/__init__.py b/handlers/essential/__init__.py new file mode 100644 index 0000000..a160ef4 --- /dev/null +++ b/handlers/essential/__init__.py @@ -0,0 +1,3 @@ +from .errors import dp + +__all__ = ["dp"] diff --git a/handlers/errors/error_handler.py b/handlers/essential/errors.py similarity index 66% rename from handlers/errors/error_handler.py rename to handlers/essential/errors.py index 57ce53c..0d71962 100644 --- a/handlers/errors/error_handler.py +++ b/handlers/essential/errors.py @@ -1,4 +1,4 @@ -import logging +from loguru import logger from loader import dp @@ -18,39 +18,39 @@ async def errors_handler(update, exception): CantParseEntities, MessageCantBeDeleted) if isinstance(exception, CantDemoteChatCreator): - logging.debug("Can't demote chat creator") + logger.debug("Can't demote chat creator") return True if isinstance(exception, MessageNotModified): - logging.debug('Message is not modified') + logger.debug('Message is not modified') return True if isinstance(exception, MessageCantBeDeleted): - logging.debug('Message cant be deleted') + logger.debug('Message cant be deleted') return True if isinstance(exception, MessageToDeleteNotFound): - logging.debug('Message to delete not found') + logger.debug('Message to delete not found') return True if isinstance(exception, MessageTextIsEmpty): - logging.debug('MessageTextIsEmpty') + logger.debug('MessageTextIsEmpty') return True if isinstance(exception, Unauthorized): - logging.info(f'Unauthorized: {exception}') + logger.info(f'Unauthorized: {exception}') return True if isinstance(exception, InvalidQueryID): - logging.exception(f'InvalidQueryID: {exception} \nUpdate: {update}') + logger.exception(f'InvalidQueryID: {exception} \nUpdate: {update}') return True if isinstance(exception, TelegramAPIError): - logging.exception(f'TelegramAPIError: {exception} \nUpdate: {update}') + logger.exception(f'TelegramAPIError: {exception} \nUpdate: {update}') return True if isinstance(exception, RetryAfter): - logging.exception(f'RetryAfter: {exception} \nUpdate: {update}') + logger.exception(f'RetryAfter: {exception} \nUpdate: {update}') return True if isinstance(exception, CantParseEntities): - logging.exception(f'CantParseEntities: {exception} \nUpdate: {update}') + logger.exception(f'CantParseEntities: {exception} \nUpdate: {update}') return True - logging.exception(f'Update: {update} \n{exception}') + logger.exception(f'Update: {update} \n{exception}') diff --git a/handlers/groups/__init__.py b/handlers/groups/__init__.py index e69de29..22747cb 100644 --- a/handlers/groups/__init__.py +++ b/handlers/groups/__init__.py @@ -0,0 +1 @@ +# __all__ = ["dp"] diff --git a/handlers/private/__init__.py b/handlers/private/__init__.py new file mode 100644 index 0000000..22747cb --- /dev/null +++ b/handlers/private/__init__.py @@ -0,0 +1 @@ +# __all__ = ["dp"] diff --git a/handlers/users/__init__.py b/handlers/users/__init__.py index 4f3c9f7..e69de29 100644 --- a/handlers/users/__init__.py +++ b/handlers/users/__init__.py @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 93cd2fe..0000000 --- a/handlers/users/echo.py +++ /dev/null @@ -1,7 +0,0 @@ -from aiogram import types -from loader import dp - - -@dp.message_handler() -async def bot_echo(message: types.Message): - await message.answer(message.text) diff --git a/handlers/users/help.py b/handlers/users/help.py deleted file mode 100644 index 7226861..0000000 --- a/handlers/users/help.py +++ /dev/null @@ -1,16 +0,0 @@ -from aiogram import types -from aiogram.dispatcher.filters.builtin import CommandHelp - -from loader import dp -from utils.misc import rate_limit - - -@rate_limit(5, 'help') -@dp.message_handler(CommandHelp()) -async def bot_help(message: types.Message): - text = [ - 'Список команд: ', - '/start - Начать диалог', - '/help - Получить справку' - ] - await message.answer('\n'.join(text)) diff --git a/handlers/users/start.py b/handlers/users/start.py deleted file mode 100644 index 54abb72..0000000 --- a/handlers/users/start.py +++ /dev/null @@ -1,9 +0,0 @@ -from aiogram import types -from aiogram.dispatcher.filters.builtin import CommandStart - -from loader 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/requirements.txt b/requirements.txt index 7613d94..91fc8ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -aiogram==2.7 -aiohttp==3.6.2 -aioredis==1.3.1 \ No newline at end of file +aiogram==2.8 +environs==8.0.0 +loguru==0.5.0 diff --git a/utils/misc/__init__.py b/utils/misc/__init__.py index 3f7248a..05dba85 100644 --- a/utils/misc/__init__.py +++ b/utils/misc/__init__.py @@ -1,2 +1 @@ from .throttling import rate_limit -from . import logging diff --git a/utils/notify_admins.py b/utils/notify_admins.py index c920196..4173724 100644 --- a/utils/notify_admins.py +++ b/utils/notify_admins.py @@ -1,14 +1,20 @@ -import logging +from loguru import logger from aiogram import Dispatcher +from aiogram.utils.exceptions import ChatNotFound -from data.config import admins +from data.config import ADMINS_ID + + +async def mail_to_admins(dp): + for admin in ADMINS_ID: + try: + await dp.bot.send_message(admin, "Бот Запущен и готов к работе с группами!") + except ChatNotFound: + logger.error("Чат с админом не найден") + except Exception as err: + logger.exception(err) 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) + await mail_to_admins(dp) diff --git a/utils/set_bot_commands.py b/utils/set_bot_commands.py new file mode 100644 index 0000000..90f3a96 --- /dev/null +++ b/utils/set_bot_commands.py @@ -0,0 +1,7 @@ +from aiogram import types + + +async def set_default_commands(dp): + await dp.bot.set_my_commands([ + types.BotCommand("start", "Начало работы с ботом"), + ])