Skip to content
This repository has been archived by the owner on Jan 1, 2021. It is now read-only.

Гайд по содержимому v3

Rhinik edited this page Apr 19, 2020 · 2 revisions

Взаимодействие

Авторизация

После установки пакета, о которой можно почитать здесь, пакет можно импортировать:

import vk_dev

Авторизация осуществляется классомvk_dev.Api(token, version, group_id=0):

import vk_dev
api = vk_dev.Auth(
    token='token',
    v=5.103,
    group_id=123456789
)

К api можно обращаться 3мя способами:

  • Через точку, что вызовет соответсвующий метод API вк.
  • С помощью asyncio.run() и тем же вызовом через точку, чтобы запустить корутину вне вашей реакции
  • Через побитовый сдвиг вправо, т.е. >>, что позволяет передать инкапсулирующим API классам и функциям ваш токен, версию, всякие айдификаторы и позволяет производить запросы от вашего имени. Все, что делает >> при побитовом сдвиге:
def __rrshift__(self, cls):
    cls.api = self
    return cls

Это позволяет создавать собственные икапсуляторы, просто определив __rrshift__. Удобный функционал по созданию подобных классов будет позже.

Обо всем подробнее чуть позже

Авторизация пользователей и сообществ проходит одинаково, за исключением передачи параметра group_id. Если вы не используете этот параметр в инициализаторе класса (оставляя значение по умолчанию), пакет будет делать все запросы от лица пользователя.

О том, что такое токен и какие они бывают, можно почитать вот здесь

Запросы к API

Запросы к API осуществляются объектом класса Api, который представляет собой перегруженные методы асинхронные __getattr__ и __call__ для легкого взаимодействия.

## Кодом выше была инициализация класса Api
from asyncio import run
users = run(api.users.get(user_ids=210700286, fields=['bdate']))

Вам следует обращаться к апи вне корутин через asyncio.run(), т.к. по умолчанию все обращения к апи работают ассинхронно.

Это позволит выполнить VK метод users.get с параметрами user_ids=1&fields=bdate. Проще говоря, состоявляется POST запрос такого вида

https://api.vk.com/method/users.get?user_ids=210700286&fields=bdate&access_token=token&v=5.103
# Условимся, что query тут -- это body запроса

Что присвоит для users словарь

{
    "id": 210700286,
    "first_name": "Lindsey",
    "last_name": "Stirling",
    "bdate": "21.9.1986"
}

Все неудачные запросы поднимут исключение vk_dev.VkErr. "Положительные" ответы от VK автоматически возвращают поле response. Вы можете получить доступ к вернувшимся параметрам, используя как синтаксис получения ключа из словаря, так и точечный способ.

Если вы не знаете, что из себя представляют методы VK, почитать об этом можно тут

LongPoll

Запуск процесса

Обработка LongPoll -- самый мощный инстуремент этого пакета. Для начала, нужно инициализировать vk_dev.LongPoll(default: bool = True, faileds: List[int] = [1, 2, 3, 4], **kwargs) (получение адреса сервера, номер события и ключ). Класс одинаковый для пользователей и групп.

lp = api >> vk_dev.LongPoll()

Разберем поля

Параметр Описание
default Устанавливает настройки LongPoll по умолчанию. О них чуть ниже
faileds Список возможных возвращаемых от LongPoll ошибок, которые следует обработать автоматически. В ином случае поднимается исключениеvk_dev.VkErr. Информация об ошибках для пользователей и сообществ
**kwargs Предусмотрено для каких-либо обновлений LongPoll. Все поля kwargs перегрывают поля default.

Настройки поля default для класса vk_dev.LongPoll

## Для пользователей
user_get = {
    'need_pts': False,
    'lp_version': 10
}
## Для сообществ
group_get = {
    # group_id
}

Чтобы запустить процесс "ловли" событий от вк, нужно вызвать этот объект, используя его __call__(self, default: bool = True, **kwargs). Одинаковый для сообществ и пользвоателей

if __name__ == '__main__':
    lp()

Поле default, также как в инициализаторе, возвращает настройки по умолчанию. Все они могут быть перекрыты через kwargs

## Для пользовтелей
user_init = {
    'wait': 25,
    'mode': 8,
    'version': 3
}
## Для сообществ
group_init = {
    'wait': 25
}

Методы обработки

Тут начинается самое интересное. Все ваши "реакции" на события должны быть выражены в форме функций с декоратором lp и указанием события Вот пример кода, отвечающий Hello user на все события message_new

import vk_dev

api = vk_dev.Api(
    token='token',
    v=5.103,
    group_id=123456789
)
lp = api >> vk_dev.LongPoll()

@lp.message_new() # Скобки обязательны
async def reaction(event, pl):
    await api.messages.send(
        peer_id=event.object.message.peer_id,
        message='Hello user',
        random_id=vk_dev.random_id()
    )

if __name__ == '__main__':
    lp()

Все реакции должны принимать 2 аргумента -- event и pl. В event будет передано само событие от LongPoll, pl же нужен для полезной нагрузки, о которой речь пойдет чуть позже.

Используя точечный синтаксис в декоратое @lp вы должны указать, какое событие должно быть поймано, чтобы функция сработала. О событиях можно ознакомиться здесь

Фукция vk_dev.random_id(between: int = 2**31) возвращает случайное число в диопазоне [-between; between]. Обязательно для отправки сообщения в параметре random_id. Однако, сообщества могут просто использовать 0. О таких полезных инструментах вы можете узнать из главы про инструменты

О параметре pl

pl, он же payload -- полезная нагрузка. Представьте, что вы обрабатываете событие, и в процессе обработки вам нужно достать какие-то данные из базы данных, какие-то получить от вк, какие-то расчитать, да и вообще неплохо было бы настругать для все этого класс и кучку полезных методов. Чтобы не делать это каждый раз, вы можете использовать аргумент pl. Для пользования этим аргументом вам нужно указать фукнцию, которая вызывается сразу после выбора события. Возвращать она должна словарь. Вы же можете можете получать значения этого словаря как с помощью синтаксиса словаря, так и точечным способом. Вот пример кода, в котором создается функция для pl. Она банально возвращает объект message, в котором есть нужные нам поля text и peer_id. Ими мы воспользуемся в реакции

import vk_dev
## Auth, api, lp...

@vk_dev.payload
def mypayload(event):
    return {
        'msg': event.object.message
    }

@lp.message_new(mypayload)
async def reaction(event, pl):
    """
    Пишет пользователю текст, который он написал нам
    """
    await api.messages.send(
        peer_id=pl.msg.peer_id,
        message=f'You said {pl.msg.text}',
        random_id=vk_dev.random_id()
    )

Ваша функция или корутина должна быть обернута в декоратор @vk_dev.payload и принимать аргумент event (их может быть сколько угодно, но event обязателен). После чего объект этой функции нужно передать в скобки @lp.message_new и вуа-ля! К слову, вы можете использовать несколько payload-генераторов для ваших реакции. При взаимодействии с одинаковыми ключами они будут перекрываться, то есть аналогично dict.update()

Фабрика декораторов

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

@lp.message_new()
def reaction(event, pl):
    if event.object.message.text.startswith(['/', '!']):
        api.messages.send(
            peer_id=event.object.message.peer_id,
            message='Hi!',
            random_id=vk_dev.random_id()
        )

Чтобы не нагромождать ваш код ветками условий, этот пакет предоставляет вам инструменты по созданию декораторов-условий, на подобие этих. Давайте перенесем нашу проверку на префикс в "инструмент"

import vk_dev
## Auth, api, lp...

from vk_dev import Condition

class Prefix(vk_dev.Condition):
    """
    Check how message start
    *prefixes: List[str]
    """

    def __init__(self, *prefixes):
        self._prefixes = prefixes

    def code(self, event, pl):
        if event.object.message.text.startswith(self._prefixes):
            return True
        return False

@Prefix('/', '!')
@lp.message_new()
async def reaction(event, pl):
    await api.messages.send(
        peer_id=event.object.message.peer_id,
        message='Hi!',
        random_id=vk_dev.random_id()
    )

Для начала, нам нужно создать класс, который будет наследоваться от vk_dev.Condition. Родитель представляет собой абстрактный класс, требующий реализовать метод code, а также уже имеющий __call__, чье поведение вам изменять нельзя. После чего вы можете определить __init__, в который передадите всех необходимые аргументы. Давайте имена таким аргументам с знака подчеркивания _. Вы не будете использовать их вне класса и это также исключит возможные конфликты с именами родительскго класса. code может быть как функцией, так и корутиной и должен принимать два аргумента -- event и pl и возвращать либо True, либо False. Теперь вы можете поместить декоратор над @lp и быть уверенным, что реакция сработает только если сообщение началось с символов / или !. Вы можете создавать ваши эекземпляры на скольких угодно реакциях и передавать им разные аргументы. К слову, пакет имеет встроенный набор таких вот условий. Они лежат в подпакете vk_dev.cond. Prefix (абсолютно идентичный) там уже есть. Полезные условия будут добавляться со временем. Если вы считаете, что созданное вами условие является очень полезной вещью -- вы можете предложить его.

Создание таких условий выигрывает по времени. Перед тем, как вызвать реакцию, пакет пробегается по всем условиям и если хотя бы один вернул ложь -- условия больше не проверяются. Причем важная вещь -- порядок ваших собственных декораторов ничего не сломает. Вы можете размещать их как угодно, но @lp должен быть первым. Поменяется лишь логика обработки событий. Будьте аккуратны с сменой значений pl внутри ваших conditions.

Для очень любопытных -- super().__init__(self) вы можете не делать. Родитель имеет init по умолчанию от класса object.

Клавиатура

Для клавиатуры предусмотрен класс vk_dev.Keyboard. На вход он принимает словарь, описывающий клавиатуру бота. Для использования просто отправьте экземпляр этого класса в параметре keyboard

@vk_dev.Prefix('/kb')
@lp.message_new()
async def keyboard(event, pl):
    from vk_dev import Keyboard
    """
    Отправит inline клавиатуру с 1 кнопкой
    """
    keyboard = {
        'inline': True,
        'buttons': [
            [
                {
                    'action': {
                        'type': 'text',
                        'payload': '{}',
                        'label': 'Some text'
                    },
                    'color': 'positive'
                }
            ]
        ]
    }
    await api.messages.send(
        peer_id=event.object.message.peer_id,
        message='Your keyboard, sir',
        keyboard=Keyboard(keyboard),
        random_id=vk_dev.random_id()
    )

Вы можете выстроить клавиатуру с помощью специальных констуркторов Button. Например

@vk_dev.cond.Prefix('/kb')
@lp.message_new()
async def kb(event, pl):
    """
    Send Keyboard
    """
    from vk_dev import Keyboard, Button

    kb =\
    Keyboard(inline=True).create(
        Button.text(payload='{}', label='Some text').positive(),
        Button.text(payload='{}', label='Text near').secondary(),
        Button.line(),
        Button.text(payload='{}', label='Text under').negative(),
        Button.text(payload='{}', label='Text in corner').primary()
    )
    await api.messages.send(
        peer_id=447532348,
        message='Your keyboard, sir.',
        keyboard=kb,
        random_id=0
    )

Инициализация пустой клавиатуры происходит тогда, когда не передан словарь в инициализатор Keyboard. Метод .create() принимает в себя кнопки. Кнопки являются объектами класса Button и работают следующим образом:

  1. Вы вы вызываете статический метод класса, тем самым указывая на action.type кнопки. К примеру .location или .text. Далее передаете осталное содержимое поля action. Если ваша кнопка типа text, она поддерживает цвета, которые можно указать, вызвав соответствующий метод. Новую строку с кнопками можно начать с помощью Button.line()

Карусели

Относительно новый функционал позволяет отправлять пользователям "карусели". Из себя они представляют набор окошечек, перемещающихся по горизонтали и имеющие что-то из заголовока, описания, фотографий или даже набор простенькой клавиатуры. Вы можете вручную сгенерировать дерево для карусели и поместить его в класс vk_dev.Template или же воспользоваться функционалом и генерировать его с помощью yield-генератора:

from vk_dev import Template, Element, Keyboard, Button
import vk_dev

@vk_dev.cond.Prefix('tp')
@lp.message_new()
async def tp(event, pl):
    """
    Отправляет Карусель пользвователю
    с продуктами в качестве ассортимента
    """
    tp = Template()

    @tp
    def elems():
        """
        """
        # Заголовки для элементов карусели
        titles = [
            'Морковь',
            'Картошка',
            'Ягоды'
        ]
        # Описания для элементов. Порядок долже сохраниться
        descs = [
            'Вытянутый оранжевый овощ. Любимец кроликов',
            'Кусок крахмала, но вкусный до безумия в пюрешке',
            'Просто наборчик полезных сладостей'
        ]
        # Клавиатура для элемента
        keyboards = [
            Keyboard().create(
                Button.text(
                    label='Купить',
                    payload={"name": name.lower()}
                )
            ) for name in titles
        ]
        # Просто проходимся фором по каждому из элементов
        for title, desc, kb in zip(titles, descs, keyboards):
            # Создаем экземпляр Element(**kwargs)
            elem = Element(
                title=title,
                description=desc,
                buttons=kb
            )
            # Yield'им элемент, не забывая добавлять событие,
            # которое должно случиться при нажатии на элемент.
            # Все описано в документации вк
            yield elem.open_link('https://google.com')

    await api.messages.send(
        peer_id=event.object.message.peer_id,
        message='Наш ассортимент',
        template=tp,
        random_id=0
    )

Для экземпляра Template() нужно вернуть генератор, который будет выплевывать элементы для карусели. Все максимально просто.

Загрузка документов (doc, graffiti, voice messages)

Загрузка документов представляется следующим образом

from vk_dev import Document
import vk_dev


@vk_dev.cond.Prefix('/doc')
@lp.message_new()
async def doc(event, pl):
    """
    Отправляет файл пользователю
    """

    with open('file.png', 'rb') as file:
        doc = api >> Document.to_message(
            peer_id=event.object.message.peer_id,
            type='doc'
        )
        doc = await doc.load(file=file)

    await api.messages.send(
        peer_id=447532348,
        message="asda",
        attachment=doc,
        random_id=vk_dev.random_id()
    )

Загрузка происходит в 2 этапа

  1. Вы указываете видимость для документа. Доступно .to_message и .to_wall_and_message. В них вы указываете параметры для соответсвующих методов docs.getMessagesUploadServer и docs.getWallUploadServer.
  2. Эвейтите корутину и передаете параметры под docs.save, но в качестве файла указываете именно ваш объект файла. Позже будет добавлена возможность загрузки документов по пути в вашей системе и по URL.

Инструменты

Полезные инстурменты, которые могу пригодиться вам. Находятся в vk_dev

Инструмент Описание
random_id(between: int = 2**31) Генерирует случаной число в диапазоне [-between; between]. Требуется для параметра random_id в messages.send
peer Пременная. Является int(2e9)