🛡️ Скрепа

Бот в группах и каналах

Что получится

Один бот будет отвечать в обычной supergroup и protected MLS group, показывать сведения об участниках, создавать ссылки с заявкой на вступление, одобрять заявки и публиковать текст в канале.

Что понадобится

Проверьте listener и профиль:

set -o pipefail
curl --fail-with-body -sS http://127.0.0.1:8081/healthz | jq .

Запускаем group/channel-бота

python3 -m venv .venv
.venv/bin/python -m pip install 'python-telegram-bot==22.8'

Сохраните как community_bot.py:

import os

from telegram import Update
from telegram.ext import (
    Application,
    ChatJoinRequestHandler,
    CommandHandler,
    ContextTypes,
    MessageHandler,
    filters,
)


async def chat_info(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    chat = await context.bot.get_chat(update.effective_chat.id)
    count = await context.bot.get_chat_member_count(chat.id)
    admins = await context.bot.get_chat_administrators(chat.id)
    await update.effective_message.reply_text(
        f"chat_id={chat.id}\ntype={chat.type}\n"
        f"members={count}\nadmins={len(admins)}"
    )


async def invite(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    link = await context.bot.create_chat_invite_link(
        chat_id=update.effective_chat.id,
        name="Заявка через community bot",
        creates_join_request=True,
    )
    await update.effective_message.reply_text(link.invite_link)


async def approve(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    request = update.chat_join_request
    await context.bot.approve_chat_join_request(
        chat_id=request.chat.id,
        user_id=request.from_user.id,
    )


async def member(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if len(context.args) != 1:
        await update.effective_message.reply_text("Использование: /member USER_ID")
        return
    value = await context.bot.get_chat_member(
        update.effective_chat.id, int(context.args[0])
    )
    await update.effective_message.reply_text(
        f"{value.user.full_name}: {value.status}"
    )


async def publish(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if len(context.args) < 2:
        await update.effective_message.reply_text(
            "Использование: /publish CHANNEL_CHAT_ID текст"
        )
        return
    channel_id = int(context.args[0])
    sent = await context.bot.send_message(channel_id, " ".join(context.args[1:]))
    await update.effective_message.reply_text(
        f"Опубликован channel_post {sent.message_id}"
    )


async def observe_channel(update: Update, _context: ContextTypes.DEFAULT_TYPE) -> None:
    print(f"channel chat_id: {update.effective_chat.id}", flush=True)


application = (
    Application.builder()
    .token(os.environ["SKREPA_BOT_TOKEN"])
    .base_url("http://127.0.0.1:8081/bot")
    .base_file_url("http://127.0.0.1:8081/file/bot")
    .build()
)
application.add_handler(CommandHandler("chat", chat_info))
application.add_handler(CommandHandler("invite", invite))
application.add_handler(CommandHandler("member", member))
application.add_handler(CommandHandler("publish", publish))
application.add_handler(ChatJoinRequestHandler(approve))
application.add_handler(MessageHandler(filters.ChatType.CHANNEL, observe_channel))
application.run_polling(
    allowed_updates=["message", "channel_post", "chat_join_request"]
)

Запустите .venv/bin/python community_bot.py.

Обычная группа и участники

  1. В клиенте Скрепы создайте обычную группу и добавьте бота.
  2. Выдайте боту административные права, включая управление приглашениями и участниками.
  3. Отправьте в группе /chat. Бот вернёт стабильный отрицательный chat_id, тип supergroup, число участников и администраторов.
  4. Возьмите numeric user ID из входящего update или журнала своего приложения и выполните /member USER_ID.

Для модерации SDK также предоставляет ban_chat_member, unban_chat_member и promote_chat_member. Эти вызовы подчиняются правам, которые выдали боту в клиенте Скрепы.

Ссылка и заявка на вступление

  1. Отправьте /invite в группе.
  2. Передайте полученную ссылку второй тестовой учётной записи.
  3. Откройте ссылку и отправьте заявку из клиента Скрепы.
  4. Запущенный пример получит chat_join_request и одобрит пользователя.

Чтобы разобрать решение вручную, удалите ChatJoinRequestHandler и вызовите approve_chat_join_request(chat_id, user_id) либо decline_chat_join_request(chat_id, user_id) из своей бизнес-логики. Обычная invite-ссылка может иметь expire_date и member_limit. Ссылка с creates_join_request=True не сочетается с member_limit.

Protected MLS group

Создайте в клиенте protected group, добавьте туда того же бота и снова отправьте /chat. Прикладной код и Telegram-объекты остаются прежними. Gateway самостоятельно вводит bot device в MLS group и хранит epoch state в постоянном state_dir. Первый вход может занять чуть больше времени; дождитесь события об участии бота перед отправкой команды.

Публикация в канале

  1. Создайте канал, добавьте бота и разрешите ему публиковать.
  2. Опубликуйте в канале короткий текст. В терминале появится отрицательный channel chat_id.
  3. В приватном диалоге с ботом отправьте /publish CHANNEL_CHAT_ID Новая публикация.
  4. Убедитесь, что текст появился в канале, а бот подтвердил message_id.

Входящие публикации доступны как update.channel_post; сообщения групп — как update.message. Изменения участия можно дополнительно получать через my_chat_member и chat_member, добавив их в allowed_updates.

Типичные ошибки

Границы поддержки

Группы отображаются как supergroup, каналы — как channel. Их ID стабилен для gateway и безопасен для JavaScript. Каналы поддерживают текстовую публикацию и административные операции. Media-публикации в каналах и forum topics пока не поддерживаются. Создание самих групп и каналов выполняется в клиенте Скрепы.