Search
Write a publication
Pull to refresh
61.67
Amvera
Amvera — облако для хостинга IT-приложений

Бот для написания постов в Телеграм. Создание и запуск

Reading time9 min
Views4K

Помимо написания постов в канал, должна быть обратная связь для предложений улучшения канала или идей для новый постов. Для обратной связи часто используются специальные сервисы, которые помогают поддерживать связь с подписчиками канала. Но не всегда хочется давать сторонним сервисам права администратора в канале. И сегодня мы попробуем не просто использовать такой сервис, а написать свой, который сможем улучшать под собственные требования и полностью контролировать его работу. В этой статье мы напишем и развернем на удаленном сервере Telegram бота, который обладает обратной связью и помогает выкладывать посты в канал через него.

Бот будет выкладывать посты через команду, либо же присылать нам предложения от подписчиков.

Функциональность бота

Бот будет присылать вам уведомления, когда будут приходить предложения от подписчиков, а также легко вы сможете отправить свой пост в канал с помощью него.

Пример работы:

После завершения разработки кода бота мы запустим его в облаке Amvera

Amvera представляет собой "app engine" и предлагает

  • Возможность деплоя через загрузку файлов в интерфейсе (или через push в привязанный репозиторий) с автоматической настройкой переменных, доменов с https и всего необходимого, что проще использования VPS.

  • Встроенные бэкапы, облачное логирование с синтаксическим поиском, метрики и алерты.

  • Бесплатное прокси до ведущих LLM и свой инференс LLaMA 70B.

  • Стартовый баланс в 111 р. на тесты.

Исходный код бота доступен по ссылке репозитория GitHub

Поехали.

Создание бота:

Зарегистрируем бота в @BotFather и получим токен по данному примеру:

Успешно! Мы создали бота, нам необходимо сохранить его токен.

Написание бота с использованием aiogram v3

Установим все необходимые библиотеки на локальном уровне. Хотя можно воспользоваться библиотекой telebot, но aiogram будет более подходящим выбором для начинающих из-за своей простоты.

pip install datetime aiogram

Переходим в командную строку и прописываем данную команду выше. 

Теперь приступим к написанию самого бота

  1. Создаём файл main.py

  2. Импортируем все необходимые библиотеки.

import asyncio  # Для работы с асинхронным программированием
import sqlite3  # Для работы с базами данных SQLite
from datetime import datetime  # Для работы с датой и временем
from aiogram import Bot, Dispatcher, F  # Импортируем классы для работы с Telegram API
from aiogram.enums import ParseMode  # Для определения режима обработки текста
from aiogram.types import (  # Импортируем типы данных для работы с сообщениями и клавиатурами
    Message,
    InlineKeyboardMarkup,
    InlineKeyboardButton,
    CallbackQuery,
    ReplyKeyboardMarkup,
    KeyboardButton,
)
from aiogram.client.default import DefaultBotProperties  # Для задания свойств бота
import os  # Для работы с переменными окружения

3. Создадим необходимые переменные

# Получаем идентификатор канала из переменной окружения
CHANNEL_ID = os.environ['CHID']
# Получаем идентификаторы администраторов из переменной окружения и сохраняем в список
ADMIN_IDS = [int(os.environ['ADID'])]


# Создаем экземпляр бота с токеном и устанавливаем режим обработки текста по умолчанию
bot = Bot(token=os.environ['TOKEN'], default=DefaultBotProperties(parse_mode=ParseMode.HTML))
# Создаем экземпляр диспетчера для обработки сообщений и событий
dp = Dispatcher()


# Устанавливаем соединение с базой данных SQLite. Важно производить сохранение именно в постоянное хранилище /data
conn = sqlite3.connect('/data/posts.db')
# Создаем объект курсора для выполнения SQL-запросов
cursor = conn.cursor()


# Создаем таблицу для хранения постов, если она не существует
cursor.execute('''
    CREATE TABLE IF NOT EXISTS posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        text TEXT NOT NULL,
        time TEXT NOT NULL
    )
''')
# Сохраняем изменения в базе данных
conn.commit()

4. Напишем функцию, которая будет проверять пользователя на администратора

# Функция для проверки, является ли пользователь администратором
def is_admin(user_id: int) -> bool:
    return user_id in ADMIN_IDS  # Возвращает True, если пользователь в списке администраторов

5. Создадим экземпляр бота

async def main():  # Основная асинхронная функция для запуска бота
    try:
        await dp.start_polling(bot)  # Запускаем опрос для получения обновлений от Telegram
    finally:
        await bot.session.close()  # Закрываем сессию бота после завершения работы
        conn.close()  # Закрываем соединение с базой данных


if __name__ == "__main__":  # Проверяем, является ли данный файл исполняемым модулем
    asyncio.run(main())  # Запускаем основную функцию в асинхронном режиме

6. Создадим обработчик команды /start

@dp.message(F.text == "/start")
async def start_command(message: Message):
    # Создаем клавиатуру с одной кнопкой "Предложить идею"
    keyboard = ReplyKeyboardMarkup(
        keyboard=[[KeyboardButton(text="Предложить идею")]],
        resize_keyboard=True  # Автоматически подстраивает размер клавиатуры
    )
    # Отправляем приветственное сообщение с инструкциями
    await message.reply(
        "Привет! Я бот для публикации постов в канале.\n"
        "Нажми на кнопку 'Предложить идею' или используй команду /post, чтобы предложить свой пост.",
        reply_markup=keyboard  # Прикрепляем клавиатуру к сообщению
    )

Это обработчик команды /start. В нём мы создаём дополнительно кнопку, которую в следующем этапе будем обрабатывать.

7. Следующим этапом напишем обработчик сообщения “Предложить идею”.

@dp.message(F.text == "Предложить идею")
async def suggest_idea(message: Message):
    # Отправляем сообщение с инструкциями по предложению поста
    await message.reply(
        "Пожалуйста, напиши свой пост, начиная с команды /post.\n"
        "Например: /post Это мой пост для канала!"
    )

8. Далее обработчик сообщений, начинающийся на /post.

@dp.message(F.text.startswith("/post"))
async def handle_post(message: Message):
    user_id = message.from_user.id  # Получаем идентификатор пользователя
    post_text = message.text[6:].strip()  # Извлекаем текст поста, убирая команду /post


    if not post_text:  # Проверяем, не пустой ли текст поста
        await message.reply("Пожалуйста, добавьте текст после команды /post")  # Запрашиваем текст
        return  # Завершаем выполнение функции


    if is_admin(user_id):  # Проверяем, является ли пользователь администратором
        # Вставляем новый пост в таблицу posts с текстом и текущим временем
        cursor.execute('INSERT INTO posts (text, time) VALUES (?, ?)',
                       (post_text, datetime.now().strftime("%H:%M")))
        conn.commit()  # Сохраняем изменения в базе данных


        # Отправляем пост в указанный канал
        await bot.send_message(
            chat_id=CHANNEL_ID,
            text=post_text
        )
        await message.reply("Пост опубликован!")  # Уведомляем пользователя об успешной публикации
    else:  # Если пользователь не является администратором
        # Создаем инлайн-клавиатуру с кнопками "Одобрить" и "Отклонить"
        keyboard = InlineKeyboardMarkup(inline_keyboard=[
            [
                InlineKeyboardButton(text="Одобрить", callback_data=f"approve_{message.message_id}_{user_id}"),
                InlineKeyboardButton(text="Отклонить", callback_data=f"reject_{message.message_id}_{user_id}")
            ]
        ])
       
        for admin_id in ADMIN_IDS:  # Цикл по всем администраторам
            # Отправляем сообщение каждому администратору о новом предложении поста
            await bot.send_message(
                chat_id=admin_id,
                text=f"Новое предложение поста от {message.from_user.full_name}:\n\n{post_text}",
                reply_markup=keyboard  # Прикрепляем клавиатуру к сообщению
            )
       
        await message.reply("Ваш пост отправлен администраторам на проверку!")  # Уведомляем пользователя о статусе его поста

С помощью функции, пользователи смогут отправить вам свою идею или же вы сами сможете опубликовать пост в телеграм-канал. Теперь есть проверка на аргументы после команды /post. Вот пример работы:

9. Далее напишем функцию, которая отвечает за одобрение/отклонение идей наших подписчиков.

@dp.callback_query(F.data.startswith("approve_"))  # Обработчик для колбек-запросов, начинающихся с "approve_"
async def approve_post(callback: CallbackQuery):  # Асинхронная функция для обработки одобрения поста
    if not is_admin(callback.from_user.id):  # Проверяем, является ли пользователь администратором
        await callback.answer("У вас нет прав для одобрения постов")  # Если нет, отправляем ответ с сообщением
        return  # Завершаем выполнение функции


    data_parts = callback.data.split("_")  # Разделяем данные колбек-запроса на части
    message_id = data_parts[1]  # Извлекаем идентификатор сообщения из данных
    user_id = int(data_parts[2])  # Извлекаем идентификатор пользователя и преобразуем в целое число
    suggested_text = callback.message.text.split("\n\n")[1]  # Извлекаем текст предложения поста


    try:
        await bot.send_message(  # Пытаемся отправить сообщение пользователю о том, что пост одобрен
            chat_id=user_id,  # Указываем идентификатор пользователя
            text="Ваш пост одобрен! Администратор скоро его опубликует."  # Текст сообщения
        )
    except Exception as e:  # Обрабатываем возможные исключения при отправке сообщения
        print(f"Ошибка при отправке сообщения пользователю: {e}")  # Выводим ошибку в консоль


    await callback.message.edit_text(  # Редактируем текст сообщения колбек-запроса
        f"{callback.message.text}\n\nСтатус: Одобрен ✅\n\nДля публикации используйте команду:\n/post {suggested_text}"  # Добавляем статус и инструкцию для публикации
    )


@dp.callback_query(F.data.startswith("reject_"))  # Обработчик для колбек-запросов, начинающихся с "reject_"
async def reject_post(callback: CallbackQuery):  # Асинхронная функция для обработки отклонения поста
    if not is_admin(callback.from_user.id):  # Проверяем, является ли пользователь администратором
        await callback.answer("У вас нет прав для отклонения постов")  # Если нет, отправляем ответ с сообщением
        return  # Завершаем выполнение функции


    data_parts = callback.data.split("_")  # Разделяем данные колбек-запроса на части
    message_id = data_parts[1]  # Извлекаем идентификатор сообщения из данных
    user_id = int(data_parts[2])  # Извлекаем идентификатор пользователя и преобразуем в целое число
   
    try:
        await bot.send_message(  # Пытаемся отправить сообщение пользователю о том, что пост отклонен
            chat_id=user_id,  # Указываем идентификатор пользователя
            text="Ваш пост был отклонен."  # Текст сообщения
        )
    except Exception as e:  # Обрабатываем возможные исключения при отправке сообщения
        print(f"Ошибка при отправке сообщения пользователю: {e}")  # Выводим ошибку в консоль
   
    await callback.message.edit_text(  # Редактируем текст сообщения колбек-запроса
        f"{callback.message.text}\n\nСтатус: Отклонен ❌"  # Добавляем статус отклонения
    )

После добавления данной функции, нам в чат с ботом будут приходить сообщения с предложениями от пользователей, мы сможем отклонить или же принять ту или иную идею. Вот пример:

Готово! Наш код вышел на: 166 строк кода.

Чтобы развернуть нашего бота на удалённом сервере, нам необходимо написать requirements.txt, который отвечает за установку всех зависимостей

Выглядеть данный файл будет так:

datetime==5.5
aiogram==3.17.0

Подготовка к запуску на удалённом сервере

Мы осуществим деплой в облаке Amvera, которое предоставляет возможность развертывания через загрузку файлов в интерфейсе или с помощью git push. Кроме того, при регистрации вам будет начислен бонусный баланс в размере 111 рублей, который позволит вам бесплатно пользоваться сервисом в течение тестового периода.

Сам деплой займет у нас не более 5 минут.

Файл amvera.yaml

Мы можем воспользоваться генератором для создания этого файла или просто указать необходимые параметры в интерфейсе личного кабинета в разделе «Конфигурация».

Выбираем окружение Python

Далее вводим версию Python, в нашем случае 3.10
Указываем путь к файлу с зависимостями, requirements.txt

Вводим имя нашего основного файла, в нашем случае main.py

Нажимаем Generate YAML и закидываем получившейся файл в репозиторий с ботом.

Вот так выглядит файл конфигурации:

version: null
meta:
  environment: python
  toolchain:
    name: pip
    version: 3.11
build:
  requirementsPath: requirements.txt
run:
  scriptName: main.py
  persistenceMount: /data
  containerPort: 80
  servicePort: 80

Деплой через интерфейс

  1. Заходим в ЛК Amvera.ru

  2. Нажимаем на кнопочку “Создать проект”.

  3. Выбираем тип сервиса — приложение, вводим название проекта и выбираем тарифный план.

  4. Нажимаем далее.

  5. Выбираем файлы, которые нужно закинуть в проект, и перемещаем их в окно загрузки.

Нажмите кнопку "Далее". Появится окно для загрузки файлов, в которое нужно перетащить необходимые файлы. Обратите внимание, что папку venv загружать не следует, так как система создаст её автоматически на основе файла с зависимостями.

В данном случае ничего не нужно делать, так как мы уже загрузили файл amvera.yaml, и все настройки выполняются автоматически. Просто нажмите «Завершить». Начнется процесс сборки, но он завершится с ошибкой, так как мы не добавили токен нашего бота в секреты.

Теперь нам необходимо добавить секрет — токен нашего бота. В методе bot.run() мы указали только слово TOKEN, но теперь нужно создать переменную в качестве секрета. Для этого перейдите на вкладку «Переменные» в проекте и нажмите «Создать секрет». В поле «Название» введите переменную TOKEN, а в поле «Значение» — сам токен.

Помимо TOKEN мы добавляем ещё несколько переменных: ADID, CHID

ADID:

Сюда мы должны написать ваш ID Telegram Account

CHID:

А сюда мы прописываем username нашего телеграм-канала.

После добавления всех необходимых переменных перезапустим проект и получим вот такой статус.

Рассмотрим альтернативный способ деплоя - деплой через Git

Не менее интересный способ деплоя и более удобный!

Процесс доставки кода с помощью команды git push amvera master позволит нам обновлять проект всего тремя командами в терминале, избавляя от необходимости заходить на сайт облачного сервиса, что делает работу гораздо более удобной.

  1. Создаём папку для проекта и помещаем в неё все необходимые файлы.

  2. Затем открываем командную строку и переходим в созданную папку с помощью команды cd "путь к папке". После этого инициализируем репозиторий, выполнив соответствующую команду.

git init

Далее заходим на Amvera и создаём новый проект

Теперь выбираем метод “Через git”.

Теперь нам нужно подключиться к существующему репозиторию, для этого копируем команду ниже и вставляем в командную строку.

Необходимо будет ввести логин и пароль от сервиса Amvera. Обратите внимание, что при вводе пароля в терминале он не будет отображаться — это сделано для повышения безопасности.

Затем вводим команды для добавления файлов и создания коммита:

git add .

Не забываем про точку в конце, она обязательна!

git commit -m "initial commit"

Запушим все файлы и сборка начнется автоматически. Для этого вводим команду:

git push amvera master

Если все сделано правильно, после сборки начнётся запуск бота для выкладки постов в Телеграм. В случае ошибки рекомендуется ознакомиться с логами сборки и приложения, а также изучить распространённые ошибки в документации сервиса.

Исходный код бота доступен в репозитории GitHub

Tags:
Hubs:
0
Comments3

Articles

Information

Website
amvera.ru
Registered
Employees
11–30 employees
Location
Россия
Representative
Кирилл Косолапов