From c5e0192d243c328f7bbcf926ed292fca68473923 Mon Sep 17 00:00:00 2001 From: mihalin Date: Sat, 3 Jul 2021 12:56:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=D0=B7=D0=B0=D0=B2=D0=B8=D1=81?= =?UTF-8?q?=D0=B8=D0=BC=D1=8B=D0=B9=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=20Instance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 10 +++++++- instance/Dockerfile | 4 ++-- instance/__init__.py | 0 instance/bot.py | 44 +++++++++++++++++++++++----------- instance/docker-compose.yaml | 23 ++++++++++++++++++ instance/requirements.txt | 3 +++ instance/settings.py | 46 ++++++++++++++++++++++++++++++++++++ olgram/settings.py | 14 +++++++---- utils/settings.py | 1 + 9 files changed, 123 insertions(+), 22 deletions(-) delete mode 100644 instance/__init__.py create mode 100644 instance/docker-compose.yaml create mode 100644 instance/requirements.txt create mode 100644 instance/settings.py create mode 100644 utils/settings.py diff --git a/bot.py b/bot.py index 53ca92b..b930509 100644 --- a/bot.py +++ b/bot.py @@ -1,7 +1,6 @@ import asyncio import aiogram.types -import tortoise.transactions from aiogram import Bot as AioBot, Dispatcher from aiogram.contrib.fsm_storage.memory import MemoryStorage @@ -27,6 +26,14 @@ async def invite_callback(identify: int, message: aiogram.types.Message): await bot.group_chats.add(chat) +async def left_callback(identify: int, message: aiogram.types.Message): + bot = await Bot.get(id=identify) + + chat = await bot.group_chats.get_or_none(chat_id=message.chat.id) + if chat: + await bot.group_chats.remove(chat) + + def run_bot(bot: BotInstance, loop: ty.Optional[asyncio.AbstractEventLoop] = None): loop = loop or asyncio.get_event_loop() loop.create_task(bot.start_polling()) @@ -39,6 +46,7 @@ async def run_all_bots(loop: asyncio.AbstractEventLoop): bot.super_chat_id, bot.start_text, invite_callback=invite_callback, + left_callback=left_callback, identify=bot.id), loop) diff --git a/instance/Dockerfile b/instance/Dockerfile index 9d36986..09b05d2 100644 --- a/instance/Dockerfile +++ b/instance/Dockerfile @@ -5,6 +5,6 @@ COPY . /app WORKDIR /app RUN pip install --upgrade pip && \ - pip install -r requirements.txt && \ + pip install -r requirements.txt -CMD ["python", "instance"] +CMD ["python", "bot.py"] diff --git a/instance/__init__.py b/instance/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/instance/bot.py b/instance/bot.py index 32db39e..339f2df 100644 --- a/instance/bot.py +++ b/instance/bot.py @@ -5,6 +5,8 @@ import aioredis from aiogram import Dispatcher, types, exceptions from aiogram.contrib.fsm_storage.memory import MemoryStorage +from settings import InstanceSettings + class BotInstance: def __init__(self, token: str, super_chat_id: int, start_text: str, @@ -26,12 +28,22 @@ class BotInstance: self._dp.stop_polling() async def start_polling(self): - self._redis = await aioredis.create_redis_pool('redis://localhost:6370') + self._redis = await aioredis.create_redis_pool(InstanceSettings.redis_path()) bot = aiogram.Bot(self._token) self._dp = Dispatcher(bot, storage=MemoryStorage()) - self._dp.register_message_handler(self._receive_text, content_types=[types.ContentType.TEXT]) + # Здесь перечислены все типы сообщений, которые бот должен пересылать + self._dp.register_message_handler(self._receive_message, content_types=[types.ContentType.TEXT, + types.ContentType.CONTACT, + types.ContentType.ANIMATION, + types.ContentType.AUDIO, + types.ContentType.DOCUMENT, + types.ContentType.PHOTO, + types.ContentType.STICKER, + types.ContentType.VIDEO, + types.ContentType.VOICE]) + # Callback-и на добавление бота в чат и удаление бота из чата self._dp.register_message_handler(self._receive_invite, content_types=[types.ContentType.NEW_CHAT_MEMBERS]) self._dp.register_message_handler(self._receive_left, content_types=[types.ContentType.LEFT_CHAT_MEMBER]) @@ -55,46 +67,50 @@ class BotInstance: if message.left_chat_member.id == message.bot.id: await self._left_callback(self._identify, message) - async def _receive_text(self, message: types.Message): + async def _receive_message(self, message: types.Message): """ - Some text received + Получено обычное сообщение, вероятно, для пересыла в другой чат :param message: :return: """ if message.text and message.text.startswith("/start"): + # На команду start нужно ответить, не пересылая сообщение никуда await message.answer(self._start_text) return if message.chat.id != self._super_chat_id: - # Это обычный чат + # Это обычный чат: сообщение нужно переслать в супер-чат new_message = await message.forward(self._super_chat_id) await self._redis.set(self._message_unique_id(new_message.message_id), message.chat.id) else: - # Это чат, в который бот должен пересылать сообщения + # Это супер-чат if message.reply_to_message: + # Ответ из супер-чата переслать тому пользователю, chat_id = await self._redis.get(self._message_unique_id(message.reply_to_message.message_id)) if not chat_id: chat_id = message.reply_to_message.forward_from_chat if not chat_id: - await message.reply("Невозможно ответить, автор сообщения не найден") + await message.reply("Невозможно переслать сообщение: автор не найден") return chat_id = int(chat_id) try: await message.copy_to(chat_id) except exceptions.MessageError: - await message.reply("Невозможно отправить сообщение пользователю: возможно, он заблокировал бота") + await message.reply("Невозможно переслать сообщение: возможно, автор заблокировал бота") return - else: await message.forward(self._super_chat_id) if __name__ == '__main__': - # Single instance mode - import os + """ + Режим single-instance. В этом режиме не работает olgram. На сервере запускается только один feedback (instance) + бот для пересылки сообщений. Все настройки этого бота задаются в переменных окружения на сервере. Бот работает + в режиме polling + """ bot = BotInstance( - os.getenv("TOKEN"), - int(os.getenv("CHAT_ID")), - os.getenv("START_TEXT") + InstanceSettings.token(), + InstanceSettings.super_chat_id(), + InstanceSettings.start_text() ) asyncio.get_event_loop().run_until_complete(bot.start_polling()) diff --git a/instance/docker-compose.yaml b/instance/docker-compose.yaml new file mode 100644 index 0000000..7579c75 --- /dev/null +++ b/instance/docker-compose.yaml @@ -0,0 +1,23 @@ +version: '3' +services: + redis: + restart: unless-stopped + image: 'bitnami/redis:latest' + environment: + - ALLOW_EMPTY_PASSWORD=yes + volumes: + - redis-db:/bitnami/redis/data + env_file: + - .env + instance: + build: . + restart: unless-stopped + depends_on: + - redis + env_file: + - .env + environment: + - INSTANCE_REDIS_PATH=redis://redis + +volumes: + redis-db: diff --git a/instance/requirements.txt b/instance/requirements.txt new file mode 100644 index 0000000..5bd6a47 --- /dev/null +++ b/instance/requirements.txt @@ -0,0 +1,3 @@ +aiogram +python-dotenv +aioredis \ No newline at end of file diff --git a/instance/settings.py b/instance/settings.py new file mode 100644 index 0000000..8b40611 --- /dev/null +++ b/instance/settings.py @@ -0,0 +1,46 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + + +class InstanceSettings: + @classmethod + def _get_env(cls, parameter: str) -> str: + parameter = os.getenv(parameter, None) + if not parameter: + raise ValueError(f"{parameter} not defined in ENV") + return parameter + + @classmethod + def token(cls) -> str: + """ + Token instance бота + :return: + """ + return cls._get_env("INSTANCE_TOKEN") + + @classmethod + def super_chat_id(cls) -> int: + """ + ID чата, в который бот пересылает сообщения + Это может быть личный чат (ID > 0) или общий чат (ID < 0) + :return: + """ + return int(cls._get_env("INSTANCE_SUPER_CHAT_ID")) + + @classmethod + def start_text(cls) -> str: + """ + Этот текст будет отправляться пользователю по команде /start + :return: + """ + return cls._get_env("INSTANCE_START_TEXT") + + @classmethod + def redis_path(cls) -> str: + """ + Путь до БД redis + :return: + """ + return cls._get_env("INSTANCE_REDIS_PATH") diff --git a/olgram/settings.py b/olgram/settings.py index 6d0f373..65de455 100644 --- a/olgram/settings.py +++ b/olgram/settings.py @@ -1,12 +1,12 @@ -from abc import ABC from dotenv import load_dotenv +from abc import ABC import os load_dotenv() -class _Settings(ABC): +class AbstractSettings(ABC): @classmethod def _get_env(cls, parameter: str) -> str: parameter = os.getenv(parameter, None) @@ -15,7 +15,7 @@ class _Settings(ABC): return parameter -class OlgramSettings(_Settings): +class OlgramSettings(AbstractSettings): @classmethod def max_bots_per_user(cls) -> int: """ @@ -25,13 +25,17 @@ class OlgramSettings(_Settings): return 5 -class BotSettings(_Settings): +class BotSettings(AbstractSettings): @classmethod def token(cls) -> str: + """ + Токен olgram бота + :return: + """ return cls._get_env("BOT_TOKEN") -class DatabaseSettings(_Settings): +class DatabaseSettings(AbstractSettings): @classmethod def user(cls) -> str: return cls._get_env("POSTGRES_USER") diff --git a/utils/settings.py b/utils/settings.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/utils/settings.py @@ -0,0 +1 @@ +