Перейти к содержанию

События и слушатели

VKFlow позволяет обрабатывать любые события VK API через слушатели (listener). Слушатели можно регистрировать как напрямую в приложении, так и внутри Cog-модулей.

Быстрый старт

import vkflow as vf

app = vf.App()

@app.listener()
async def on_message_new(payload):
    print(f"Новое сообщение: {payload}")

app.run("TOKEN")

Имя события определяется по имени функции — префикс on_ убирается автоматически.

Регистрация через app.listener()

Подходит для небольших ботов без Cog-модулей:

import vkflow as vf

app = vf.App()

# По имени функции (on_ убирается)
@app.listener()
async def on_message_new(payload):
    print(f"Новое сообщение: {payload}")

# С явным именем события
@app.listener("message_reply")
async def handle_reply(payload, user_id):
    print(f"Ответ от {user_id}")

# Событие готовности бота
@app.listener()
async def on_ready(bot):
    mention = await bot.mention()
    print(f"Бот запущен: {mention}")

app.run("TOKEN")

Регистрация в Cog

Для организованных проектов слушатели размещаются внутри Cog-классов:

from vkflow import commands

class Events(commands.Cog):
    @commands.listener()
    async def on_message_new(self, payload):
        print(f"Новое сообщение!")

    @commands.listener("message_reply")
    async def handle_reply(self, user_id, text):
        print(f"Ответ от {user_id}: {text}")

    @commands.listener()
    async def on_ready(self, bot):
        mention = await bot.mention()
        print(f"Бот запущен: {mention}")

self обязателен

В Cog первый параметр — всегда self, остальные — параметры события.

Алиасы событий

VKFlow поддерживает короткие имена для часто используемых событий:

Алиас Событие VK
message message_new
callback message_event
typing message_typing_state
ready ready (внутреннее)
@app.listener()
async def on_callback(payload):
    # Эквивалентно on_message_event
    print(f"Callback-кнопка: {payload}")

Инъекция параметров

Слушатели автоматически извлекают нужные данные из события по именам параметров:

@app.listener()
async def on_message_new(payload, peer_id, from_id, text):
    # payload — весь объект события (dict)
    # peer_id, from_id, text — поля из payload
    print(f"[{peer_id}] {from_id}: {text}")

Специальные параметры:

Параметр Значение
payload Весь объект события (event.object)
event Объект NewEvent целиком
bot Экземпляр Bot

Все остальные имена параметров извлекаются из словаря payload.

Chat-action события

VKFlow автоматически распознаёт действия в чатах (приглашение, кик, закрепление и т.д.) и оборачивает их в удобные event-классы.

Типы chat-action событий

Событие Класс VK action types
member_join MemberJoinEvent chat_invite_user, chat_invite_user_by_link, chat_invite_user_by_message_request
member_remove MemberRemoveEvent chat_kick_user
pin_message PinMessageEvent chat_pin_message
unpin_message UnpinMessageEvent chat_unpin_message
chat_edit ChatEditEvent chat_photo_update, chat_photo_remove, chat_title_update
chat_create ChatCreateEvent chat_create

Использование

from vkflow import commands

class ChatEvents(commands.Cog):
    @commands.listener()
    async def on_member_join(self, event: commands.MemberJoinEvent):
        await event.ctx.reply(f"Добро пожаловать, {event.member_id}!")

        # Дополнительные свойства
        if event.is_self_return:
            print("Пользователь вернулся сам")
        elif event.is_by_link:
            print("Пользователь зашёл по ссылке")

    @commands.listener()
    async def on_member_remove(self, event: commands.MemberRemoveEvent):
        if event.is_self_leave:
            await event.ctx.reply("Пользователь покинул чат")
        elif event.is_kicked:
            await event.ctx.reply(f"Пользователь {event.member_id} кикнут")

    @commands.listener()
    async def on_pin_message(self, event: commands.PinMessageEvent):
        print(f"Сообщение {event.conversation_message_id} закреплено")

    @commands.listener()
    async def on_chat_edit(self, event: commands.ChatEditEvent):
        if event.is_title_update:
            print(f"Название чата изменено: {event.text}")
        elif event.is_photo_update:
            print("Фото чата обновлено")

Или напрямую в приложении:

@app.listener()
async def on_member_join(event: commands.MemberJoinEvent):
    await event.ctx.reply(f"Добро пожаловать, {event.member_id}!")

Свойства event-классов

MemberJoinEvent:

Свойство Тип Описание
member_id int ID вступившего пользователя
inviter_id int \| None ID пригласившего (если есть)
is_self_return bool Пользователь вернулся сам
is_by_link bool Вступил по ссылке
is_by_request bool Вступил по заявке

MemberRemoveEvent:

Свойство Тип Описание
member_id int ID удалённого пользователя
kicker_id int \| None ID кикнувшего (если есть)
is_self_leave bool Пользователь ушёл сам
is_kicked bool Пользователь был кикнут

PinMessageEvent / UnpinMessageEvent:

Свойство Тип Описание
member_id int ID выполнившего действие
conversation_message_id int ID сообщения в беседе
message str Текст сообщения (только PinMessageEvent)

ChatEditEvent:

Свойство Тип Описание
text str \| None Новое значение (название/URL фото)
is_title_update bool Изменение названия
is_photo_update bool Обновление фото

У всех event-классов есть общие свойства: ctx, bot, api, payload.

Алиасы chat-action событий

Можно слушать группу событий или конкретный VK action type:

# Группа: member_join ловит invite_user + invite_by_link + invite_by_request
@app.listener()
async def on_member_join(event): ...

# Конкретный тип
@app.listener()
async def on_chat_invite_user_by_link(event): ...

# Конкретный тип через явное имя
@app.listener("chat_invite_user")
async def on_invite(event): ...

Raw-режим

Префикс raw_ (или on_raw_) отключает обёртки event-классов и передаёт сырые данные:

from vkflow import commands

class RawEvents(commands.Cog):
    # Сырое VK-событие
    @commands.listener()
    async def on_raw_message_new(self, payload, from_id, text):
        print(f"Raw: {from_id} написал {text}")

    # Сырое chat-action (dict вместо event-класса)
    @commands.listener()
    async def on_raw_member_join(self, payload, member_id):
        print(f"Raw: user {member_id} joined")

В raw-режиме параметры извлекаются напрямую из словаря payload по именам.

Ожидание событий (wait_for)

wait_for позволяет приостановить выполнение и дождаться конкретного события:

import vkflow as vf

app = vf.App()

@app.command("подтверди")
async def confirm(ctx: vf.Context):
    await ctx.send("Нажмите callback-кнопку для подтверждения")

    try:
        event = await app.wait_for(
            "message_event",
            timeout=30,
            check=lambda e: e.event.object.get("user_id") == ctx.author
        )
        await ctx.send("Подтверждено!")
    except vf.EventTimeoutError:
        await ctx.send("Время вышло!")

Параметры wait_for

Параметр Тип Описание
event_name str Имя события для ожидания
timeout float \| None Таймаут в секундах (None — без ограничения)
check Callable \| None Функция-фильтр, должна вернуть True для нужного события

wait_for также доступен через контекст:

@app.command("имя")
async def ask_name(ctx: vf.Context):
    await ctx.send("Как тебя зовут?")

    try:
        reply = await ctx.wait_for_message(timeout=30)
        await ctx.send(f"Привет, {reply.msg.text}!")
    except vf.EventTimeoutError:
        await ctx.send("Время вышло!")

Ручная отправка событий (dispatch_event)

Можно программно генерировать события:

# Отправить кастомное событие
await app.dispatch_event("my_custom_event", data="hello")

# Слушатель поймает его
@app.listener("my_custom_event")
async def on_custom(payload):
    print(f"Получено: {payload}")

Низкоуровневый on_event

Для прямого доступа к NewEvent без инъекции параметров:

from vkflow.event import GroupEvent

@app.on_event(GroupEvent.MESSAGE_NEW)
async def raw_handler(event):
    # event — объект NewEvent
    print(event.event.object)

on_event принимает EventType (enum) и передаёт в обработчик полный NewEvent.

Полный пример

import vkflow as vf
from vkflow import commands

app = vf.App(prefixes=["!"])

class MyEvents(commands.Cog):
    """Обработка всех событий бота"""

    @commands.listener()
    async def on_ready(self, bot):
        mention = await bot.mention()
        print(f"Бот запущен: {mention}")

    @commands.listener()
    async def on_message_new(self, payload):
        text = payload.get("text", "")
        if "привет" in text.lower():
            print("Кто-то поздоровался!")

    @commands.listener()
    async def on_member_join(self, event: commands.MemberJoinEvent):
        await event.ctx.reply(
            f"Добро пожаловать! Твой ID: {event.member_id}"
        )

    @commands.listener()
    async def on_member_remove(self, event: commands.MemberRemoveEvent):
        if event.is_kicked:
            await event.ctx.reply("Пользователь был кикнут")

    @commands.listener()
    async def on_pin_message(self, event: commands.PinMessageEvent):
        await event.ctx.reply(f"Сообщение закреплено: {event.message}")

@app.on_startup()
async def setup(bot):
    await app.add_cog(MyEvents())

app.run("TOKEN")