Skip to content

ExpressApp/pybotx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

pybotx

Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° для создания Ρ‡Π°Ρ‚-Π±ΠΎΡ‚ΠΎΠ² ΠΈ SmartApps для мСссСндТСра eXpress

PyPI version PyPI - Python Version Coverage Code style

ΠžΡΠΎΠ±Π΅Π½Π½ΠΎΡΡ‚ΠΈ

  • ΠŸΡ€ΠΎΡΡ‚Π°Ρ для использования
  • ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ коллбэки BotX
  • Π›Π΅Π³ΠΊΠΎ интСгрируСтся с асинхронными Π²Π΅Π±-Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠ°ΠΌΠΈ
  • ПолноС ΠΏΠΎΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ тСстами
  • ПолноС ΠΏΠΎΠΊΡ€Ρ‹Ρ‚ΠΈΠ΅ аннотациями Ρ‚ΠΈΠΏΠΎΠ²

Установка

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ poetry:

poetry add pybotx

ΠŸΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π΅Π½ΠΈΠ΅: Π”Π°Π½Π½Ρ‹ΠΉ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ находится Π² Π°ΠΊΡ‚ΠΈΠ²Π½ΠΎΠΉ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ (0.y.z) ΠΈ Π΅Π³ΠΎ API ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ ΠΈΠ·ΠΌΠ΅Π½Ρ‘Π½ ΠΏΡ€ΠΈ ΠΏΠΎΠ²Ρ‹ΡˆΠ΅Π½ΠΈΠΈ ΠΌΠΈΠ½ΠΎΡ€Π½ΠΎΠΉ вСрсии.

Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ мСссСндТСрС eXpress ΠΈ ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ΅ BotX

Π”ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΡŽ ΠΏΠΎ мСссСндТСру (Π²ΠΊΠ»ΡŽΡ‡Π°Ρ руководство ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΈ администратора) ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ Π½Π° ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΌ сайтС.

ΠŸΠ΅Ρ€Π΅Π΄ Ρ‚Π΅ΠΌ, ΠΊΠ°ΠΊ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ знакомство с Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΎΠΉ pybotx, совСтуСм ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ ΡΡ‚Π°Ρ‚ΡŒΠΈ: Π§Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅ Ρ‡Π°Ρ‚-Π±ΠΎΡ‚Ρ‹ ΠΈ SmartApp ΠΈ ВзаимодСйствиС с Bot API ΠΈ BotX API. Π’ этих ΡΡ‚Π°Ρ‚ΡŒΡΡ… находятся ΠΈΡΡ‡Π΅Ρ€ΠΏΡ‹Π²Π°ΡŽΡ‰ΠΈΠ΅ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Ρ€Π°Π±ΠΎΡ‚Ρ‹ с ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠΎΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π»Π΅Π³ΠΊΠΎ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚ΡŒ, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ pybotx.

Π’Π°ΠΊΠΆΠ΅ Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ лишним ΠΎΠ·Π½Π°ΠΊΠΎΠΌΠΈΡ‚ΡŒΡΡ с Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠ΅ΠΉ ΠΏΠΎ ΠΏΠ»Π°Ρ„ΠΎΡ€ΠΌΠ΅ BotX .

ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ Π³ΠΎΡ‚ΠΎΠ²Ρ‹Ρ… ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ΠΎΠ² Π½Π° Π±Π°Π·Π΅ pybotx

  • Next Feature Bot - Π±ΠΎΡ‚, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹ΠΉ для тСстирования Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»Π° ΠΏΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΡ‹ BotX.
  • ToDo Bot - Π±ΠΎΡ‚ для вСдСния списка Π΄Π΅Π».
  • Weather SmartApp - ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ для просмотра ΠΏΠΎΠ³ΠΎΠ΄Ρ‹.

ΠœΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Π±ΠΎΡ‚Π° (интСграция с FastAPI)

from http import HTTPStatus
from uuid import UUID

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

# Π’ этом ΠΈ ΠΏΠΎΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΡ… ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°Ρ… ΠΈΠΌΠΏΠΎΡ€Ρ‚ ΠΈΠ· `pybotx` Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚ΡŒΡΡ
# Ρ‡Π΅Ρ€Π΅Π· Π·Π²Ρ‘Π·Π΄ΠΎΡ‡ΠΊΡƒ для краткости. Однако, это Π½Π΅ являСтся Ρ…ΠΎΡ€ΠΎΡˆΠ΅ΠΉ ΠΏΡ€Π°ΠΊΡ‚ΠΈΠΊΠΎΠΉ.
from pybotx import *

collector = HandlerCollector()


@collector.command("/echo", description="Send back the received message body")
async def echo_handler(message: IncomingMessage, bot: Bot) -> None:
    await bot.answer_message(message.body)


# Бюда ΠΌΠΎΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ свои ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ ΠΊΠΎΠΌΠ°Π½Π΄
# ΠΈΠ»ΠΈ ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ΠΊΠΎΠ΄Π°, располоТСнныС Π½ΠΈΠΆΠ΅.


bot = Bot(
    collectors=[collector],
    bot_accounts=[
        BotAccountWithSecret(
            # НС Π·Π°Π±ΡƒΠ΄ΡŒΡ‚Π΅ Π·Π°ΠΌΠ΅Π½ΠΈΡ‚ΡŒ эти ΡƒΡ‡Ρ‘Ρ‚Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ Π½Π° настоящиС,
            # ΠΊΠΎΠ³Π΄Π° создадитС Π±ΠΎΡ‚Π° Π² ΠΏΠ°Π½Π΅Π»ΠΈ администратора.
            id=UUID("123e4567-e89b-12d3-a456-426655440000"),
            cts_url="https://cts.example.com",
            secret_key="e29b417773f2feab9dac143ee3da20c5",
        ),
    ],
)

app = FastAPI()
app.add_event_handler("startup", bot.startup)
app.add_event_handler("shutdown", bot.shutdown)


# На этот эндпоинт приходят ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ BotX
# (сообщСния ΠΈ систСмныС события).
@app.post("/command")
async def command_handler(request: Request) -> JSONResponse:
    bot.async_execute_raw_bot_command(
        await request.json(),
        request_headers=request.headers,
    )
    return JSONResponse(
        build_command_accepted_response(),
        status_code=HTTPStatus.ACCEPTED,
    )


# На этот эндпоинт приходят события BotX для SmartApps, ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌΡ‹Π΅ синхронно.
@app.post("/smartapps/request")
async def sync_smartapp_event_handler(request: Request) -> JSONResponse:
    response = await bot.sync_execute_raw_smartapp_event(
        await request.json(),
        request_headers=request.headers,
    )
    return JSONResponse(response.jsonable_dict(), status_code=HTTPStatus.OK)


# К этому эндпоинту BotX обращаСтся, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ·Π½Π°Ρ‚ΡŒ
# Π΄ΠΎΡΡ‚ΡƒΠΏΠ½ΠΎΡΡ‚ΡŒ Π±ΠΎΡ‚Π° ΠΈ Π΅Π³ΠΎ список ΠΊΠΎΠΌΠ°Π½Π΄.
@app.get("/status")
async def status_handler(request: Request) -> JSONResponse:
    status = await bot.raw_get_status(
        dict(request.query_params),
        request_headers=request.headers,
    )
    return JSONResponse(status)


# На этот эндпоинт приходят коллбэки с Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°ΠΌΠΈ
# выполнСния асинхронных ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² Π² BotX.
@app.post("/notification/callback")
async def callback_handler(request: Request) -> JSONResponse:
    await bot.set_raw_botx_method_result(
        await request.json(),
        verify_request=False,
    )
    return JSONResponse(
        build_command_accepted_response(),
        status_code=HTTPStatus.ACCEPTED,
    )

ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹

ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ сообщСний

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from uuid import UUID

from pybotx import *

ADMIN_HUIDS = (UUID("123e4567-e89b-12d3-a456-426614174000"),)

collector = HandlerCollector()


@collector.command("/visible", description="Visible command")
async def visible_handler(_: IncomingMessage, bot: Bot) -> None:
    # ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π±ΠΎΡ‚Π°. Команда видимая, поэтому описаниС
    # являСтся ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌ.
    print("Hello from `/visible` handler")


@collector.command("/_invisible", visible=False)
async def invisible_handler(_: IncomingMessage, bot: Bot) -> None:
    # НСвидимая ΠΊΠΎΠΌΠ°Π½Π΄Π° - Π½Π΅ отобраТаСтся Π² спискС ΠΊΠΎΠΌΠ°Π½Π΄ Π±ΠΎΡ‚Π°
    # ΠΈ Π½Π΅ нуТдаСтся Π² описании.
    print("Hello from `/invisible` handler")


async def is_admin(status_recipient: StatusRecipient, bot: Bot) -> bool:
    return status_recipient.huid in ADMIN_HUIDS


@collector.command("/admin-command", visible=is_admin)
async def admin_command_handler(_: IncomingMessage, bot: Bot) -> None:
    # Команда показываСтся Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ являСтся Π°Π΄ΠΌΠΈΠ½ΠΎΠΌ.
    # Бписок ΠΊΠΎΠΌΠ°Π½Π΄ Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Π΅Ρ‚ΡΡ ΠΏΡ€ΠΈ ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΈΠΈ Ρ‡Π°Ρ‚Π° Π² ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΈ.
    print("Hello from `/admin-command` handler")


@collector.default_message_handler
async def default_handler(_: IncomingMessage, bot: Bot) -> None:
    # Если ΠΊΠΎΠΌΠ°Π½Π΄Π° Π½Π΅ Π±Ρ‹Π»Π° Π½Π°ΠΉΠ΄Π΅Π½Π°, вызываСтся `default_message_handler`,
    # Ссли ΠΎΠ½ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½. Π’Π°ΠΊΠΎΠΉ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄ΠΈΠ½.
    print("Hello from default handler")

ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ систСмных событий

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from pybotx import *

collector = HandlerCollector()


@collector.chat_created
async def chat_created_handler(event: ChatCreatedEvent, bot: Bot) -> None:
    # Π Π°Π±ΠΎΡ‚Π° с событиями производится с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Ρ… ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ².
    # На ΠΊΠ°ΠΆΠ΄ΠΎΠ΅ событиС ΠΌΠΎΠΆΠ½ΠΎ ΠΎΠ±ΡŠΡΠ²ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄ΠΈΠ½ Ρ‚Π°ΠΊΠΎΠΉ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ.
    print(f"Got `chat_created` event: {event}")


@collector.smartapp_event
async def smartapp_event_handler(event: SmartAppEvent, bot: Bot) -> None:
    print(f"Got `smartapp_event` event: {event}")

ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ синхронных SmartApp событий

from pybotx import *

collector = HandlerCollector()


# ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ синхронных Smartapp событий, приходящих Π½Π° эндпоинт `/smartapps/request`
@collector.sync_smartapp_event
async def handle_sync_smartapp_event(
    event: SmartAppEvent, bot: Bot,
) -> BotAPISyncSmartAppEventResultResponse:
    print(f"Got sync smartapp event: {event}")
    return BotAPISyncSmartAppEventResultResponse.from_domain(
        data={},
        files=[],
    )

Middlewares

(Π­Ρ‚ΠΎΡ‚ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π» относится ΠΈΡΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΊ pybotx)

from httpx import AsyncClient

from pybotx import *

collector = HandlerCollector()


async def custom_api_client_middleware(
    message: IncomingMessage,
    bot: Bot,
    call_next: IncomingMessageHandlerFunc,
) -> None:
    # Π”ΠΎ Π²Ρ‹Π·ΠΎΠ²Π° `call_next` (обязатСлСн Π² ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΌΠΈΠ΄Π΄Π»Π²Π°Ρ€ΠΈ) располагаСтся
    # ΠΊΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выполняСтся Π΄ΠΎ Ρ‚ΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ сообщСниС Π΄ΠΎΠΉΠ΄Ρ‘Ρ‚ Π΄ΠΎ
    # своСго ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°.
    async_client = AsyncClient()

    # Π£ сообщСния Π΅ΡΡ‚ΡŒ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ состояния, Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΠΈΠ΄Π΄Π»Π²Π°Ρ€ΠΈ ΠΌΠΎΠ³ΡƒΡ‚ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ
    # Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΡ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅.
    message.state.async_client = async_client

    await call_next(message, bot)

    # ПослС Π²Ρ‹Π·ΠΎΠ²Π° `call_next` выполняСтся ΠΊΠΎΠ΄, ΠΊΠΎΠ³Π΄Π° ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΡƒΠΆΠ΅
    # Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ» свою Ρ€Π°Π±ΠΎΡ‚Ρƒ.
    await async_client.aclose()


@collector.command(
    "/fetch-resource",
    description="Fetch resource from passed URL",
    middlewares=[custom_api_client_middleware],
)
async def fetch_resource_handler(message: IncomingMessage, bot: Bot) -> None:
    async_client = message.state.async_client
    response = await async_client.get(message.argument)
    print(response.status_code)

Π‘Π±ΠΎΡ€Ρ‰ΠΈΠΊΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ²

(Π­Ρ‚ΠΎΡ‚ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π» относится ΠΈΡΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΊ pybotx)

from uuid import UUID, uuid4

from pybotx import *

ADMIN_HUIDS = (UUID("123e4567-e89b-12d3-a456-426614174000"),)


async def request_id_middleware(
    message: IncomingMessage,
    bot: Bot,
    call_next: IncomingMessageHandlerFunc,
) -> None:
    message.state.request_id = uuid4()
    await call_next(message, bot)


async def ensure_admin_middleware(
    message: IncomingMessage,
    bot: Bot,
    call_next: IncomingMessageHandlerFunc,
) -> None:
    if message.sender.huid not in ADMIN_HUIDS:
        await bot.answer_message("You are not admin")
        return

    await call_next(message, bot)


# Для Ρ‚ΠΎΠ³ΠΎ Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π½ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹,
# Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ экзСмпляр класса `HandlerCollector`.
# ПозТС этот сборщик Π±ΡƒΠ΄Π΅Ρ‚ использован ΠΏΡ€ΠΈ создании Π±ΠΎΡ‚Π°.
main_collector = HandlerCollector(middlewares=[request_id_middleware])

# Π£ сборщиков (ΠΊΠ°ΠΊ Ρƒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ²), ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ собствСнныС ΠΌΠΈΠ΄Π΄Π»Π²Π°Ρ€ΠΈ.
# Они автоматичСски ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡŽΡ‚ΡΡ ΠΊΠΎ всСм ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°ΠΌ Π΄Π°Π½Π½ΠΎΠ³ΠΎ сборщика.
admin_collector = HandlerCollector(middlewares=[ensure_admin_middleware])

# Π‘Π±ΠΎΡ€Ρ‰ΠΈΠΊΠΈ ΠΌΠΎΠΆΠ½ΠΎ Π²ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ Π΄Ρ€ΡƒΠ³ Π² Π΄Ρ€ΡƒΠ³Π°. Π’ Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ Ρƒ
# `admin_collector` Π±ΡƒΠ΄ΡƒΡ‚ Π΄Π²Π΅ ΠΌΠΈΠ΄Π΄Π»Π²Π°Ρ€ΠΈ. ΠŸΠ΅Ρ€Π²Π°Ρ - Π΅Π³ΠΎ собствСнная,
# вторая - получСнная ΠΏΡ€ΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ Π² `main_collector`.
main_collector.include(admin_collector)

ΠžΡ‚ΠΏΡ€Π°Π²ΠΊΠ° сообщСния

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from uuid import UUID

from pybotx import *

collector = HandlerCollector()


@collector.command("/answer", description="Answer to sender")
async def answer_to_sender_handler(message: IncomingMessage, bot: Bot) -> None:
    # Π’.ΠΊ. Π½Π°ΠΌ извСстно, ΠΎΡ‚ΠΊΡƒΠ΄Π° ΠΏΡ€ΠΈΡˆΠ»ΠΎ сообщСниС, Ρƒ `pybotx` Π΅ΡΡ‚ΡŒ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΡ‹ΠΉ
    # контСкст для ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ ΠΎΡ‚Π²Π΅Ρ‚Π°.
    await bot.answer_message("Text")


@collector.command("/send", description="Send message to specified chat")
async def send_message_handler(message: IncomingMessage, bot: Bot) -> None:
    try:
        chat_id = UUID(message.argument)
    except ValueError:
        await bot.answer_message("Invalid chat id")
        return

    # Π’ Π΄Π°Π½Π½ΠΎΠΌ случаС нас интСрСсуСт Π½Π΅ ΠΎΡ‚Π²Π΅Ρ‚, Π° ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠ° сообщСния
    # Π² Π΄Ρ€ΡƒΠ³ΠΎΠΉ Ρ‡Π°Ρ‚. Π§Π°Ρ‚ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ ΠΈ Π±ΠΎΡ‚ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π² Π½Ρ‘ΠΌ.
    try:
        await bot.send_message(
            bot_id=message.bot.id,
            chat_id=chat_id,
            body="Text",
        )
    except Exception as exc:
        await bot.answer_message(f"Error: {exc}")
        return

    await bot.answer_message("Message was send")


@collector.command("/prebuild-answer", description="Answer with prebuild message")
async def prebuild_answer_handler(message: IncomingMessage, bot: Bot) -> None:
    # Π‘ ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ OutgoingMessage ΠΌΠΎΠΆΠ½ΠΎ Π²Ρ‹Π½ΠΎΡΠΈΡ‚ΡŒ Π»ΠΎΠ³ΠΈΠΊΡƒ
    # формирования ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ² Π² Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠΎΠ΄ΡƒΠ»ΠΈ.
    answer = OutgoingMessage(
        bot_id=message.bot.id,
        chat_id=message.chat.id,
        body="Text",
    )
    await bot.send(message=answer)

ΠžΡ‚ΠΏΡ€Π°Π²ΠΊΠ° сообщСния с ΠΊΠ½ΠΎΠΏΠΊΠ°ΠΌΠΈ

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from pybotx import *

collector = HandlerCollector()


@collector.command("/bubbles", description="Send buttons")
async def bubbles_handler(message: IncomingMessage, bot: Bot) -> None:
    # Если Π²Π°ΠΌ Π½ΡƒΠΆΠ½Π° ΠΊΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Π° ΠΏΠΎΠ΄ ΠΏΠΎΠ»Π΅ΠΌ для Π²Π²ΠΎΠ΄Π° сообщСния,
    # ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ `KeyboardMarkup`. Π­Ρ‚ΠΎΡ‚ класс ΠΈΠΌΠ΅Π΅Ρ‚ Ρ‚Π΅ ΠΆΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹,
    # Ρ‡Ρ‚ΠΎ ΠΈ `BubbleMarkup`.
    bubbles = BubbleMarkup()
    bubbles.add_button(
        command="/choose",
        label="Red",
        data={"pill": "red"},
        background_color="#FF0000",
    )
    bubbles.add_button(
        command="/choose",
        label="Blue",
        data={"pill": "blue"},
        background_color="#0000FF",
        new_row=False,
    )

    # Π’ ΠΊΠ½ΠΎΠΏΠΊΡƒ ΠΌΠΎΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ ссылку Π½Π° рСсурс,
    # для этого Π½ΡƒΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ url Π² Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ `link`, Π° `command` ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ пустым,
    # `alert` добавляСтся Π² ΠΎΠΊΠ½ΠΎ подтвСрТдСния ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Π΅ ΠΏΠΎ ссылкС.
    bubbles.add_button(
        label="Bubble with link",
        alert="alert text",
        link="https://example.com",
    )

    await bot.answer_message(
        "The time has come to make a choice, Mr. Anderson:",
        bubbles=bubbles,
    )

Π£ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from pybotx import *

collector = HandlerCollector()


@collector.command("/send-contact", description="Send author's contact")
async def send_contact_handler(message: IncomingMessage, bot: Bot) -> None:
    contact = MentionBuilder.contact(message.sender.huid)
    await bot.answer_message(f"Author is {contact}")


@collector.command("/echo-contacts", description="Send back recieved contacts")
async def echo_contact_handler(message: IncomingMessage, bot: Bot) -> None:
    if not (contacts := message.mentions.contacts):
        await bot.answer_message("Please send at least one contact")
        return

    answer = ", ".join(map(str, contacts))
    await bot.answer_message(answer)

ΠžΡ‚ΠΏΡ€Π°Π²ΠΊΠ° Ρ„Π°ΠΉΠ»Π° Π² сообщСнии

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from aiofiles.tempfile import NamedTemporaryFile

from pybotx import *

collector = HandlerCollector()


@collector.command("/send-file", description="Send file")
async def send_file_handler(message: IncomingMessage, bot: Bot) -> None:
    # Для создания Ρ„Π°ΠΉΠ»Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ file-like object
    # с ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ асинхронных ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ.
    async with NamedTemporaryFile("wb+") as async_buffer:
        await async_buffer.write(b"Hello, world!\n")
        await async_buffer.seek(0)

        file = await OutgoingAttachment.from_async_buffer(async_buffer, "test.txt")

    await bot.answer_message("Attached file", file=file)


@collector.command("/echo-file", description="Echo file")
async def echo_file_handler(message: IncomingMessage, bot: Bot) -> None:
    if not (attached_file := message.file):
        await bot.answer_message("Attached file is required")
        return

    await bot.answer_message("", file=attached_file)

Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ сообщСния

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from pybotx import *

collector = HandlerCollector()


@collector.command("/increment", description="Self-updating widget")
async def increment_handler(message: IncomingMessage, bot: Bot) -> None:
    if message.source_sync_id:  # ID сообщСния, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π±Ρ‹Π»Π° Π½Π°ΠΆΠ°Ρ‚Π° ΠΊΠ½ΠΎΠΏΠΊΠ°.
        current_value = message.data["current_value"]
        next_value = current_value + 1
    else:
        current_value = 0
        next_value = 1

    answer_text = f"Counter: {current_value}"
    bubbles = BubbleMarkup()
    bubbles.add_button(
        command="/increment",
        label="+",
        data={"current_value": next_value},
    )

    if message.source_sync_id:
        await bot.edit_message(
            bot_id=message.bot.id,
            sync_id=message.source_sync_id,
            body=answer_text,
            bubbles=bubbles,
        )
    else:
        await bot.answer_message(answer_text, bubbles=bubbles)

Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ сообщСния

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from pybotx import *

collector = HandlerCollector()


@collector.command("/deleted-message", description="Self-deleted message")
async def deleted_message_handler(message: IncomingMessage, bot: Bot) -> None:
    if message.source_sync_id:  # ID сообщСния, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ Π±Ρ‹Π»Π° Π½Π°ΠΆΠ°Ρ‚Π° ΠΊΠ½ΠΎΠΏΠΊΠ°.
        await bot.delete_message(
            bot_id=message.bot.id,
            sync_id=message.source_sync_id,
        )
        return

    bubbles = BubbleMarkup()
    bubbles.add_button(
        command="/deleted-message",
        label="Delete",
    )

    await bot.answer_message("Self-deleted message", bubbles=bubbles)

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ ошибок

(Π­Ρ‚ΠΎΡ‚ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π» относится ΠΈΡΠΊΠ»ΡŽΡ‡ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΊ pybotx)

from loguru import logger

from pybotx import *


async def internal_error_handler(
    message: IncomingMessage,
    bot: Bot,
    exc: Exception,
) -> None:
    logger.exception("Internal error:")

    await bot.answer_message(
        "**Error:** internal error, please contact your system administrator",
    )


# Для ΠΏΠ΅Ρ€Π΅Ρ…Π²Π°Ρ‚Π° ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΉ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‚ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ.
# Π‘ΠΎΡ‚ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ ΠΈΠ· Ρ‚ΠΈΠΏΠΎΠ² ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΉ ΠΈ ΠΈΡ… ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ².
bot = Bot(
    collectors=[],
    bot_accounts=[],
    exception_handlers={Exception: internal_error_handler},
)

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Ρ‡Π°Ρ‚Π°

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from pybotx import *

collector = HandlerCollector()


@collector.command("/create-group-chat", description="Create group chat")
async def create_group_chat_handler(message: IncomingMessage, bot: Bot) -> None:
    if not (contacts := message.mentions.contacts):
        await bot.answer_message("Please send at least one contact")
        return

    try:
        chat_id = await bot.create_chat(
            bot_id=message.bot.id,
            name="New group chat",
            chat_type=ChatTypes.GROUP_CHAT,
            huids=[contact.entity_id for contact in contacts],
        )
    except (ChatCreationProhibitedError, ChatCreationError) as exc:
        await bot.answer_message(str(exc))
        return

    chat_mention = MentionBuilder.chat(chat_id)
    await bot.answer_message(f"Chat created: {chat_mention}")

Поиск ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

import dataclasses

from pybotx import *

collector = HandlerCollector()


@collector.command("/my-info", description="Get info of current user")
async def search_user_handler(message: IncomingMessage, bot: Bot) -> None:
    try:
        user_info = await bot.search_user_by_huid(
            bot_id=message.bot.id,
            huid=message.sender.huid,
        )
    except UserNotFoundError:  # Если ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ ΠΈ Π±ΠΎΡ‚ находятся Π½Π° Ρ€Π°Π·Π½Ρ‹Ρ… CTS
        await bot.answer_message("User not found. Maybe you are on a different cts.")
        return

    await bot.answer_message(f"Your info:\n{dataclasses.asdict(user_info)}\n")

ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ списка ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ

(ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ)

from pybotx import *

collector = HandlerCollector()


@collector.command("/get_users_list", description="Get a list of users")
async def users_list_handler(message: IncomingMessage, bot: Bot) -> None:
    async with bot.users_as_csv(
        bot_id=message.bot.id,
        cts_user=True,
        unregistered=False,
        botx=False,
    ) as users:
        async for user in users:
            print(user)

About

A little python framework for building bots and smartapps for eXpress

Resources

License

Stars

Watchers

Forks

Packages

No packages published