Пример Instance бота, разные команды

This commit is contained in:
mihalin 2021-06-29 15:29:41 +03:00
parent 4e70ebf32c
commit d738da2fa9
15 changed files with 221 additions and 33 deletions

View File

@ -5,7 +5,7 @@ Open-source self-hosted Livegram alternative
#####
таблицы
Боты: токен, имя, владелец
Пользователи: идентификатор
instance поведение
Кто-то написал сообщение в любом чате - переслать в супер-чат
Кто-то ответил на сообщение в супер-чате - переслать автору сообщения
Кто-то написал /start - отправить стартовое сообщение

3
bot.py
View File

@ -6,7 +6,7 @@ from settings import BotSettings
from olgram.bot.bots import router as bots_router
from olgram.bot.start import router as start_router
from olgram.bot.bot import router as bot_router
from olgram.utils.database import init_database
@ -21,6 +21,7 @@ def main():
start_router.setup(dp)
bots_router.setup(dp)
bot_router.setup(dp)
executor.start_polling(dp, skip_updates=True)

View File

@ -7,9 +7,18 @@ services:
- POSTGRES_PASSWORD=test_passwd
- POSTGRES_DB=olgram
ports:
- '5431:5432'
- '5430:5432'
volumes:
- database:/var/lib/postgresql/data
redis:
image: 'bitnami/redis:latest'
environment:
- ALLOW_EMPTY_PASSWORD=yes
volumes:
- redis-db:/bitnami/redis/data
ports:
- '6370:6379'
volumes:
database:
redis-db:

10
instance/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM python:3.8-buster
COPY . /app
WORKDIR /app
RUN pip install --upgrade pip && \
pip install -r requirements.txt && \
CMD ["python", "instance"]

0
instance/__init__.py Normal file
View File

77
instance/bot.py Normal file
View File

@ -0,0 +1,77 @@
import asyncio
import aioredis
import typing as ty
from aiogram import Bot, Dispatcher, executor, types, exceptions
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from olgram.utils.router import Router
token = "(token)"
bot_id = token.split(":")[0]
start_text = 'Здравствуйте! Напишите тут что-то'
super_chat_id = -1 # ID чата здесь
router = Router()
redis: ty.Optional[aioredis.Redis] = None
def message_unique_id(message_id) -> str:
return bot_id + "-" + str(message_id)
@router.message_handler(content_types=[types.ContentType.ANY])
async def receive_text(message: types.Message):
"""
Some text received
:param message:
:return:
"""
if message.text and message.text.startswith("/start"):
await message.answer(start_text)
return
if message.chat.id != super_chat_id:
# Это обычный чат
new_message = await message.forward(super_chat_id)
await redis.set(message_unique_id(new_message.message_id), message.chat.id)
else:
# Это чат, в который бот должен пересылать сообщения
if message.reply_to_message:
chat_id = await redis.get(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("Невозможно ответить, автор сообщения не найден")
return
chat_id = int(chat_id)
try:
await message.copy_to(chat_id)
except exceptions.MessageError:
await message.reply("Невозможно отправить сообщение пользователю: возможно, он заблокировал бота")
return
else:
await message.forward(super_chat_id)
async def init_redis():
global redis
redis = await aioredis.create_redis_pool('redis://localhost:6370')
def main():
"""
Classic polling
"""
asyncio.get_event_loop().run_until_complete(init_redis())
bot = Bot(token)
dp = Dispatcher(bot, storage=MemoryStorage())
router.setup(dp)
executor.start_polling(dp, skip_updates=True)
if __name__ == '__main__':
main()

46
olgram/bot/bot.py Normal file
View File

@ -0,0 +1,46 @@
from aiogram import types, Bot as AioBot
from aiogram.dispatcher import FSMContext
from aiogram.utils.callback_data import CallbackData
from textwrap import dedent
from olgram.utils.router import Router
from olgram.utils.mix import try_delete_message
from olgram.models.models import Bot, User
router = Router()
# Пользователь выбрал бота
select_bot = CallbackData('bot_select', 'bot_id')
# Пользователь выбрал, что хочет сделать со своим ботом
bot_operation = CallbackData('bot_operation', 'operation')
@router.callback_query_handler(select_bot.filter())
async def select_bot_callback(call: types.CallbackQuery, callback_data: dict, state: FSMContext):
"""
Пользователь выбрал бота для редактирования
"""
bot_id = callback_data["bot_id"]
bot = await Bot.get_or_none(id=bot_id)
if not bot or (await bot.owner).telegram_id != call.from_user.id:
await call.answer("Такого бота нет", show_alert=True)
return
async with state.proxy() as proxy:
proxy["bot"] = bot
await try_delete_message(call.message)
keyboard = types.InlineKeyboardMarkup(row_width=2)
keyboard.insert(types.InlineKeyboardButton(text="Текст", callback_data=bot_operation.new(operation="text")))
keyboard.insert(types.InlineKeyboardButton(text="Чат", callback_data=bot_operation.new(operation="chat")))
keyboard.insert(types.InlineKeyboardButton(text="Удалить бот", callback_data=bot_operation.new(operation="delete")))
keyboard.insert(types.InlineKeyboardButton(text="<<Вернуться к списку ботов",
callback_data=bot_operation.new(operation="back")))
await AioBot.get_current().send_message(call.message.chat.id, dedent(f"""
Управление ботом @{bot.name}.
Если у вас возникли вопросы по настройке бота, то посмотрите нашу справку /help.
"""), reply_markup=keyboard)

View File

@ -5,40 +5,65 @@ import re
from textwrap import dedent
from ..utils.router import Router
from olgram.models.bot import Bot
from olgram.models.user import User
from .bot import select_bot
from olgram.models.models import Bot, User
from olgram.settings import OlgramSettings
router = Router()
token_pattern = r'[0-9]{8,10}:[a-zA-Z0-9_-]{35}'
@router.message_handler(commands=["my_bots"])
@router.message_handler(commands=["mybots"], state="*")
async def my_bots(message: types.Message, state: FSMContext):
"""
Команда /mybots (список ботов)
"""
user = await User.get_or_none(telegram_id=message.from_user.id)
bots = await Bot.filter(owner=user)
if not bots:
await message.answer(dedent("""
У вас нет добавленных ботов.
Отправьте команду /add_bot, чтобы добавить бот.
Отправьте команду /addbot, чтобы добавить бот.
"""))
return
bots_str = "\n".join(["@" + bot.name for bot in bots])
await message.answer(dedent(f"""
Ваши боты:
{bots_str}
"""))
keyboard = types.InlineKeyboardMarkup(row_width=2)
for bot in bots:
keyboard.insert(types.InlineKeyboardButton(text="@" + bot.name, callback_data=select_bot.new(bot_id=bot.id)))
await message.answer("Ваши боты", reply_markup=keyboard)
@router.message_handler(commands=["add_bot"])
@router.message_handler(commands=["addbot"], state="*")
async def add_bot(message: types.Message, state: FSMContext):
await message.answer("Окей, пришли тогда токен plz")
"""
Команда /addbot (добавить бота)
"""
bot_count = await Bot.filter(user__telegram_id=message.from_user.id).count()
if bot_count > OlgramSettings.max_bots_per_user():
await message.answer("У вас уже слишком много ботов")
return
await message.answer(dedent("""
Чтобы подключить бот, вам нужно выполнить три действия:
1. Перейдите в бот @BotFather, нажмите START и отправьте команду /newbot
2. Введите название бота, а потом username бота.
3. После создания бота перешлите ответное сообщение в этот бот или скопируйте и пришлите token бота.
Важно: не подключайте боты, которые используются в других сервисах (Manybot, Chatfuel, Livegram и других).
Подробную инструкцию по созданию бота читайте здесь /help)
"""))
await state.set_state("add_bot")
@router.message_handler(state="add_bot", content_types="text", regexp="^[^/].+") # Not command
async def bot_added(message: types.Message, state: FSMContext):
"""
Пользователь добавляет бота и мы ждём от него токен
"""
token = re.findall(token_pattern, message.text)
async def on_invalid_token():
@ -66,6 +91,7 @@ async def bot_added(message: types.Message, state: FSMContext):
try:
test_bot = AioBot(token)
test_bot_info = await test_bot.get_me()
await test_bot.session.close()
except ValueError:
return await on_invalid_token()
except Unauthorized:

View File

@ -1,4 +1,4 @@
from aiogram import Bot, Dispatcher, executor, types
from aiogram import types
from aiogram.dispatcher import FSMContext
from textwrap import dedent
from ..utils.router import Router
@ -9,7 +9,7 @@ router = Router()
@router.message_handler(commands=["start"], state="*")
async def start(message: types.Message, state: FSMContext):
"""
Start command handler
Команда /start
"""
await state.reset_state()
@ -20,8 +20,8 @@ async def start(message: types.Message, state: FSMContext):
Используйте эти команды, чтобы управлять этим ботом:
/add_bot - добавить бот
/my_bots - управление ботами
/addbot - добавить бот
/mybots - управление ботами
/help - помощь
@ -32,7 +32,7 @@ async def start(message: types.Message, state: FSMContext):
@router.message_handler(commands=["help"], state="*")
async def help(message: types.Message, state: FSMContext):
"""
Help command handler
Команда /help
"""
await message.answer(dedent("""
<todo: help here>

View File

@ -10,3 +10,11 @@ class Bot(Model):
class Meta:
table = 'bot'
class User(Model):
id = fields.IntField(pk=True)
telegram_id = fields.IntField(index=True, unique=True)
class Meta:
table = 'user'

View File

@ -1,9 +0,0 @@
from tortoise import Model, fields
class User(Model):
id = fields.IntField(pk=True)
telegram_id = fields.IntField(index=True, unique=True)
class Meta:
table = 'user'

View File

@ -15,6 +15,16 @@ class _Settings(ABC):
return parameter
class OlgramSettings(_Settings):
@classmethod
def max_bots_per_user(cls) -> int:
"""
Максимальное количество ботов у одного пользователя
:return: int
"""
return 5
class BotSettings(_Settings):
@classmethod
def token(cls) -> str:

View File

@ -8,8 +8,8 @@ async def init_database():
# which contain models from "app.models"
await Tortoise.init(
db_url=f'postgres://{DatabaseSettings.user()}:{DatabaseSettings.password()}'
f'@localhost:5431/{DatabaseSettings.database_name()}',
modules={'models': ['olgram.models.bot', 'olgram.models.user']}
f'@localhost:5430/{DatabaseSettings.database_name()}',
modules={'models': ['olgram.models.models']}
)
# Generate the schema
await Tortoise.generate_schemas()

9
olgram/utils/mix.py Normal file
View File

@ -0,0 +1,9 @@
from aiogram.types import Message
from aiogram.utils.exceptions import TelegramAPIError
async def try_delete_message(message: Message):
try:
await message.delete()
except TelegramAPIError:
pass

View File

@ -2,3 +2,4 @@ aiogram
tortoise-orm[asyncpg]
aerich
python-dotenv
aioredis