asyncVK is asynchronous library for creating a bot in VK
Project description
asyncVK – асинхронный фреймворк для создания ботов ВК. Преимущества: удобство, скорость выигрываемая за счёт асинхронности.
Бот создаётся за счёт пяти основных структурных единиц:
- Bot – это самая главная структурная единица. Это собственно сам бот, который подаёт ивенты обработчикам.
- Handler – эта структурная единица отвечает за обработку ивентов.
- Dispatcher – эта структурная единица отвечает за взаимодействие с ВК (ответы на сообщения, добавление комментариев). Она автоматически настраивается хандлерами.
- Condition (Condition, And, Or) – эта структурная единица отвечает за условия. С помощью неё можно строить сложные условия для хандлеров.
- Chain - эта структурная единица позволяет создавать цепочки команд.
Также есть такие второстепенные структурные единицы как:
- Keyboard – это второстепенная структурная единица. Она отвечает за создание кнопок в ВК.
- Message - структура сообщения для облегчения работы с сообщениями.
Как работать с библиотекой? Легко и интуитивно понятно! Для начала нужно импортировать саму библиотеку и создать бота:
from asyncVK import Handler, Bot, run_polling
from asyncVK.dispatcher import Dispatcher
from asyncVK.condition import Condition, And, Or
TOKEN = "access_token"
GROUP_ID = 182801600
bot = Bot(TOKEN, GROUP_ID)
Теперь мы можем запустить бота на LongPoll API
:
if __name__ == "__main__":
run_polling(bot)
Сейчас бот запущен, но ни на что не реагирует. Чтобы это исправить нам нужно создать обработчик и добавить его в бота. Как это сделать? Вот так:
@bot.handle
@Handler.on.message_new(Condition(command="привет!"), is_lower=True)
async def handler(dispatcher: Dispatcher):
await dispatcher.send_message("Hi!")
В примере выше мы создали обработчик новых сообщений с помощью декоратора @Handler.on.message_new
и добавили его в бота с помощью декоратора @bot.handle
.
Вместо декоратора @bot.handle
можно конечно прописать bot.handle(handler)
Как работают хандлеры (обработчики)? Когда мы засовываем экземпляр класса Bot в функцию run_polling
, мы как бы активируем метод bot.run_polling
. bot.run_polling
это бесконечный цикл. В экземпляре класса Bot есть список всех хандлеров, которые мы создали и добавили в него. И когда приходит какой-то ивент (событие), то этот цикл пересылает это событие всем хандлерам. И потом если условие истинно, то активируется функция, из которой мы сделали хандлер.
Что делает эта асинхронная функция? Она на сообщение "привет!" (в любом регистре) будет отвечать в тот же чат сообщением "Hi!". Как сделать чтобы она ответила не в тот же чат, а в ЛС? Легко! Заменить
await dispatcher.send_message("Hi!")
на
await dispatcher.answer("Hi!")
Как строить условия? Какие условия можно построить? Строить условия легко, и можно построить абсолютно любые условия! Например мы хотим, чтобы бот отвечал на привет. В таком случае мы пишем:
Condition(command="привет")
Или мы хотим, чтобы бот отвечал на привет или если peer_id
равен 2000000001. В таком случае мы пишем:
Condition(command="привет", peer_id=2000000001)
То есть прописывая дополнительные условия в Condition, мы как бы делаем if command == "привет" or peer_id == 2000000001
. Также можно аналогично прописать вот так:
Or(Condition(command="привет"), Condition(peer_id=2000000001))
Но лучше так не делать, лучше подобные условия прописывать без Or.
А что если мы хотим, чтобы бот отвечал либо если ему написали "привет", либо если в сообщении есть строка "а" и написал это пользователь с id 386746383. Тоже легко! Вот так:
Or(
Condition(command="привет"),
And(Condition(contains_command="a"), Condition(user_id=386746383))
)
И так, давайте разбирать как же строить так любые запросы. Если мы пропишем несколько аргументов в Condition
, то это будет ИЛИ (or). Если же мы засунем несколько условий в And
, то тут условие будет истинным если все условия в And
истинны, то есть это И (and). Если же мы засунем несколько условий в Or
, то условие будет истинным если истинно хотя бы одно условие в нём, то есть это ИЛИ (or). В And
и Or
можно засовывать как и Condition
, так и другие And
и Or
.
Стоит также заметить, что хандлер без условия будет срабатывать всегда, когда активируется нужное событие (то есть вне зависимости от условий, наверное потому что их нет) - @Handler.on.message_new()
Вот все аргументы Condition:
command – проверяет на равенство текста (если сообщение, то текста сообщения и т.д.) с этим аргументом.
contains_command – проверяет на то, есть ли строка contains_command в тексте.
user_id – проверяет на равенство id пользователя, инициировавшего событие, и этим аргументом.
peer_id – проверяет на равенство id чата с этим аргументом.
post_id – проверяет на равенство id записи на стене/id записи в обсуждениях с этим аргументом.
owner_id – проверяет на равенство id сообщества, где произошло событие (если событие было в сообществе).
Вот весь список хандлеров:
Handler.on.message_new – новое сообщение.
Handler.on.message_edit – редактирование сообщения.
Handler.on.wall_reply_new – новые комментарий на стене.
Handler.on.wall_reply_edit – редактирование комментария на стене.
Handler.on.wall_post_new – новый пост на стене.
Handler.on.board_post_new – новый комментарий в обсуждениях.
Handler.on.board_post_edit – редактирование комментария в обсуждениях.
Списки их аргументов абсолютно идентичны.
Возможности диспетчера:
dispatcher.answer – ответить в ЛС. Можно активировать при любом событии, отправит сообщение инициатору события. Список аргументов:
text – текст сообщения.
attachment – вложение сообщения (в виде части ссылки такого рода:
от ссылки https://vk.com/id386746383?z=photo386746383_457256628%2Falbum386746383_0
берём только photo386746383_457256628 и передаём это в качестве аргумента).
keyboard – кнопки ВК.
Возвращает структуру Message вашего сообщения.
dispatcher.reply - ответить на сообщение пользователя. Список аргументов и возвращаемое значение идентичны с answer.
dispatcher.send_message – ответить в том же чате. Список аргументов и возвращаемое значение идентичны с answer.
dispatcher.send_comment – ответить в комментариях. Список аргументов идентичен с answer, но аргумент keyboard отсутствует.
dispatcher.mark_as_read – пометить сообщение как "прочитанное". Никаких аргументов не принимает.
dispatcher.set_typing_status – установить статус на набор текста / запись голосового сообщения. Принимает один аргумент:
typing_status. Его значение по умолчанию "typing" (набор текста). Можно изменить на "audiomessage" – запись голосового сообщения.
dispatcher.kick_user - удаляет участника из беседы
member_id - id участника беседы (id сообщества пишется со знаком -)
dispatcher.edit_chat_name - изменяет название беседы
title - новое название беседы
Пример использования структуры Message:
@bot.handle
@Handler.on.message_new(Condition(command="прив"))
async def handler(dispatcher: Dispatcher):
message = await dispatcher.reply("Это сообщение исчезнет через 3 секунды, а твоё сообщение будет закреплено")
await asyncio.sleep(1)
await message.edit("Это сообщение исчезнет через 2 секунды, а твоё сообщение будет закреплено")
await asyncio.sleep(1)
await message.edit("Это сообщение исчезнет через 1 секунду, а твоё сообщение будет закреплено")
await asyncio.sleep(1)
await message.delete()
await dispatcher.message.pin()
Можно строить любые запросы, даже если этого не предполагает отсутствие метода в диспетчере:
@bot.handle
@Handler.on.message_new(Condition(contains_command="прив"), is_lower=True)
async def handler(dispatcher: Dispatcher):
result = await bot.execute("messages.send", peer_id=dispatcher.peer_id,
message="okay", random_id=0)
print(result)
В этом примере мы на новое сообщение, содержащее "прив" отвечаем "okay" нашим построенным запросом. peer_id
же берём из диспетчера.
Параметры диспетчера:
dispatcher.token - ваш токен
dispatcher.user_id - id инициировавшего событие пользователя
dispatcher.peer_id - id чата (если это ЛС, то peer_id равен user_id)
dispatcher.post_id - id записи на стене или обсуждения (если событие это новая запись на стене, новый комментарий на стене или в обсуждении)
dispatcher.owner_id - если событие было внутри группы, то owner_id это id группы
dispatcher.object_id - id объекта события
dispatcher.event - объект события
dispatcher.text - если к примеру событие это новое сообщение, то text это текст сообщения, если это к примеру новый комментарий, то text это текст комментария и т.д.
dispatcher.reply_text - текст отвеченного сообщения (если таковое имеется)
dispatcher.reply_user_id - id пользователя написавшего отмеченное сообщение (если таковое имеется)
dispatcher.reply_peer_id - id чата отмеченного сообщения (если таковое имеется)
dispatcher.reply_object_id - id объекта ответа
dispatcher.action_type - тип действия
dispatcher.action_text - текст действия
dispatcher.action_object_id - id объекта действия
dispatcher.action_member_id - id пользователя, инициировавшего действие
dispatcher.payload - payload
dispatcher.message - выдаёт структуру Message для сообщения из события
dispatcher.reply_message - выдаёт структуру Message для отвеченного сообщения из события
Возможность структуры Message:
message.edit - изменить сообщение
text - новый текст
attachment - вложение
keyboard - клавиатура
message.pin - закрепить сообщение
message.delete - удалить сообщение
Если вы хотите выполнить сразу несколько запросов асинхронно, то можно просто воспользовать библиотекой asyncio
. К примеру:
@bot.handle
@Handler.on.message_new(Condition(contains_command="прив"), is_lower=True)
async def handler(dispatcher: Dispatcher):
tasks = [asyncio.create_task(dispatcher.mark_as_read()),
asyncio.create_task(dispatcher.set_typing_status()),
asyncio.create_task(asyncio.sleep(9))]
await asyncio.gather(*tasks)
await dispatcher.send_message("okay")
@bot.handle
@Handler.on.message_new(Condition(contains_command="а"), is_lower=True)
async def handler(dispatcher: Dispatcher):
await dispatcher.send_message("Б!")
Хандлер, обрабатывающий сообщение, где есть строка "прив" сперва пометит сообщение как прочитанное, потом установит статус "печатает…" и через 9 секунд отправит сообщение "okay" и всё это асинхронно.
P.S. хандлеры друг друга не блокируют, так что во время работы первого хандлера вы можете написать "а" и бот ответит "Б!", несмотря на работу первого хандлера.
По-мимо этого можно делать хандлеры не для условий, а для всего события целиком. Например:
@bot.handle
@Handler.on("message_new")
async def handler(dispatcher: Dispatcher):
if dispatcher.text.lower() == "abs":
await dispatcher.send_message("peer")
elif dispatcher.text.lower() == "help me":
await dispatcher.send_message("no")
Этот хандлер будет обрабатывать все события типа message_new
. В данном случае он на "abs" будет отвечать "peer", а на "help me" будет отвечать "no". И также регистр сообщения не важен, ибо мы применили метод lower
.
Так можно делать обработчики для любых событий. К примеру обработчик для новых комментариев:
@bot.handle
@Handler.on("wall_reply_new")
async def handler(dispatcher: Dispatcher):
if dispatcher.text.lower() == "nice":
await dispatcher.send_comment("ok")
elif dispatcher.text.lower() == "not bad":
await dispatcher.send_comment("no, very bad!")
Какой обработчик использовать? Для условий или для всего события целиком? Если вам нужно сделать обработчик для простых команд (ответить на то этим и что-то в этом роде), то лучше все эти команды прописать в обработчике события, в данном случае это будет @Handler.on("message_new")
ведь нам нужно отвечать на сообщения. А если же команды сложные, а не простые ответы с какими-то дополнительными действиями, то лучше их прописать в обработчике условия. К примеру нам нужно, чтобы при сообщении "статистика" бот получил статистику откуда-то, рассортировал и отфильтровал её и потом отправил. Такое лучше прописывать в обработчике условия, в данном случае @Handler.on.message_new(Condition(command="статистика"))
Но нужно смотреть на код в целом, ибо иногда может пригодится сделать исключение и написать сложную команду в обработчик события, а простую в обработчик условия. То есть выбор должен зависеть от ситуации и структуры вашего кода.
В хандлерах и в самом боте не предусмотрена синхронизация. Поэтому если вы будете пользоваться асинхронной реализацией, к примеру какой-то базы-данных, будет состояние гонки. А пользоваться синхронными реализациями базы-данных плохая идея, это снизит скорость бота. Такая структура позволяет боту быть очень быстрым. Но в фреймворке есть реализация асинхронной базы-данных с синхронизацией, которой если вы будете правильно пользоваться, то состояния гонки не будет и бот будет оставаться таким же быстрым. Пример бота с этой реализацией бд:
from asyncVK.asyncDB import SQLite
db = SQLite("data.db")
bot = Bot(TOKEN, GROUP_ID)
async def create_db():
async with db:
await db.execute("""
CREATE TABLE IF NOT EXISTS profile (
user_id INTEGER,
money INTEGER
)
""")
@bot.handle
@Handler.on("message_new")
async def handler(dispatcher: Dispatcher):
if dispatcher.text.lower() == "create db" and dispatcher.user_id == OWNER_ID:
await create_db()
await dispatcher.send_message("db was created!")
elif dispatcher.text.lower() == "register":
async with db:
await db.execute("""
INSERT INTO profile
VALUES (?, 0)
""", (dispatcher.user_id,))
await dispatcher.send_message("you are was registered!")
@bot.handle
@Handler.on.message_new(Condition(command="click"), is_lower=True)
async def handler(dispatcher: Dispatcher):
async with db:
await db.execute("""
UPDATE profile
SET money=money+1
WHERE user_id=(?)
""", (dispatcher.user_id,))
state = await db.execute("""
SELECT money
FROM profile
WHERE user_id=(?)
""", (dispatcher.user_id,))
money = state[0][0]
await dispatcher.send_message(f"Money: {money}")
OWNER_ID
это константа, которая должна ваш ID в ВК, это условие запрещает создавать база-данных кому-либо кроме вас командой.
Что делает async with db
?. async with db
ждёт пока база-данных откроется для запросов, потом закрывает базу-данных для запросов и как все ваши запросы прошли к базе, она опять открывает базу-данных для запросов.
Метод db.execute
отправляет ваш запрос к базе-данных.
Также можно использовать глобальные переменные с синхронизацией. Вот пример:
from asyncVK.asyncDB import Variable
total_money = Variable(0)
@bot.handle
@Handler.on.message_new(Condition(command="/click"), is_lower=True)
async def handler(dispatcher: Dispatcher):
async with total_money:
total_money.object += 1
await dispatcher.send_message("Все деньги мира: " + str(total_money.object))
В фреймворке также присутствует встроенный функционал создания цепочек. Что такое цепочки? Это когда команда состоит из нескольких частей. То есть, к примеру, регистрация. Вы пишите /регистрация
и бот далее просит вас ввести имя. И вы вводите своё имя и регистрация пройдена в 2 сообщения, то есть 2 части.
from asyncVK.chain import Chain
Таким образом мы импортируем класс цепочек. Как создать цепочку? Всё просто. Вместо bot.handle
используем chain.add_handler
, а потом bot.add_chain(chain)
.
К примеру:
chain = Chain()
@chain.add_handler
@Handler.on.message_new(Condition(contains_command="прив"), is_lower=True)
async def handler_1(dispatcher: Dispatcher):
await dispatcher.send_message("Напиши что-то")
@chain.add_handler
@Handler.on.message_new(Condition(contains_command="что-то"), is_lower=True)
async def handler_2(dispatcher: Dispatcher):
await dispatcher.send_message("Пон")
bot.add_chain(chain)
Или:
chain = Chain()
@Handler.on.message_new(Condition(contains_command="прив"), is_lower=True)
async def handler_1(dispatcher: Dispatcher):
await dispatcher.send_message("Напиши что-то")
@Handler.on.message_new(Condition(contains_command="что-то"), is_lower=True)
async def handler_2(dispatcher: Dispatcher):
await dispatcher.send_message("Пон")
if __name__ == "__main__":
chain = Chain()
chain.add_handler(handler_1)
chain.add_handler(handler_2)
bot.add_chain(chain)
run_polling(bot)
Также можно пробрасывать какие-то данные по цепочке:
chain = Chain()
@Handler.on.message_new(Condition(contains_command="прив"), is_lower=True)
async def handler_1(dispatcher: Dispatcher):
await dispatcher.send_message("Напиши что-то")
return 12
@Handler.on.message_new(Condition(contains_command="что-то"), is_lower=True)
async def handler_2(dispatcher: Dispatcher):
await dispatcher.send_message(f"Пон {dispatcher.chain_data}")
if __name__ == "__main__":
chain = Chain()
chain.add_handler(handler_1)
chain.add_handler(handler_2)
bot.add_chain(chain)
run_polling(bot)
В таком случае после прив
бот ответит Напиши что-то
, и если после этого вы сразу напишете что-то
, то бот ответит Пон 12
. Также напоминаю, что одновременно у одного пользователя может быть только одна активная цепочка. Если же две будут активироваться по одинаковому условию, то всё равно активна будет одна из них.
Также вместо произвольных данных в цепочке можно возвращать команды. В данном случае Reject
- это полностью сбросить цепочку и Reset
- текущий хандлер сработает ещё раз. При Reset
ваш chain_data
не сбрасывается.
К примеру
from asyncVK.chain import Chain, Reset
chain = Chain()
@Handler.on.message_new(Condition(contains_command="прив"), is_lower=True)
async def handler_1(dispatcher: Dispatcher):
await dispatcher.send_message("Напиши что-то")
@Handler.on.message_new(is_lower=True)
async def handler_2(dispatcher: Dispatcher):
if dispatcher.text != "что-то":
await dispatcher.send_message("Я жду от тебя что-то")
return Reset()
await dispatcher.send_message("Пон")
if __name__ == "__main__":
chain = Chain()
chain.add_handler(handler_1)
chain.add_handler(handler_2)
bot.add_chain(chain)
run_polling(bot)
Во втором хандлере бот будет ждать от тебя слова что-то, и пока ты его не напишешь - он будет срабатывать снова и снова в этой цепочке.
Пример использования структуры Message
и ActionCondition
, бот примет закреплённое сообщение и будет его удерживать в закрепе.
ActionCondition
это условие на действие.
Возможные значения ActionCondition
:
chat_pin_message - закрепление сообщения
chat_unpin_message - открепление сообщения
chat_title_update - обновление названия беседы
chat_photo_update - обновление аватарки беседы
chat_photo_remove - удаление аватарки беседы
chat_kick_user - удаление пользователя из чата, пользователь вышел из чата
chat_invite_user - добавление пользователя в чат
pinned_message = {"object": None}
@bot.handle
@Handler.on.message_new(Condition(contains_command="удержи"))
async def handler(dispatcher: Dispatcher):
pinned_message["object"] = dispatcher.reply_message
await pinned_message["object"].pin()
await dispatcher.send_message("ok")
@bot.handle
@Handler.on.message_new(Or(ActionCondition(action="chat_pin_message"),
ActionCondition(action="chat_unpin_message")))
async def handler(dispatcher: Dispatcher):
await dispatcher.send_message("Не, не выйдет")
await pinned_message["object"].pin()
Методы диспетчера send_message
, answer
, reply
возвращают структуру Message
Пример создания кнопок, а также использования пайлоада через functional_condition
def check_payload(event_params: dict) -> bool:
payload = event_params["payload"]
if payload.get("answer") == "yes":
return True
return False
@bot.handle
@Handler.on.message_new(Condition(contains_command="дай"))
async def handler(dispatcher: Dispatcher):
keyboard = Keyboard(
Line(
Button("да", "positive", {"answer": "yes"})
),
Line(
Button("нет", "negative", {"answer": "no"}),
Button("возможно", "default", {"answer": "maybe"})
), one_time=True, inline=True
)
await dispatcher.send_message("Не дам", keyboard=keyboard)
@bot.handle
@Handler.on.message_new(Condition(functional_condition=check_payload))
async def handler(dispatcher: Dispatcher):
await dispatcher.send_message("Ок")
await dispatcher.send_message(f"Ваш пайлоад: {dispatcher.payload}")
Также существует PayloadCondition
, который в отличие от простого Condition
принимает все аргументы за И, а не ИЛИ по-умолчанию.
@bot.handle
@Handler.on.message_new(Condition(contains_command="дай"))
async def handler(dispatcher: Dispatcher):
keyboard = Keyboard(
Line(
Button("да", "positive", {"answer": "yes", "module": "vk"})
),
Line(
Button("нет", "negative", {"answer": "no"}),
Button("возможно", "default", {"answer": "yes"})
), one_time=True, inline=True
)
await dispatcher.send_message("Не дам", keyboard=keyboard)
@bot.handle
@Handler.on.message_new(PayloadCondition(answer="yes", module="vk"))
async def handler(dispatcher: Dispatcher):
await dispatcher.send_message("Ок")
await dispatcher.send_message(f"Ваш пайлоад: {dispatcher.payload}")
В библиотеке есть возможность создавать шаблоны сообщений для дальнейшей их рассылки
@bot.handle
@Handler.on.message_new(Condition(contains_command="разошли им"), is_lower=True)
async def handler(dispatcher: Dispatcher):
await dispatcher.reply("Окей")
message = dispatcher.create_message_template("Вам послание, месье", forward=dispatcher.forward)
await message.send_to("476393332,584575899")
В этом коде бот перешлёт сообщение двум пользователям с соответствующими id.
Можно отправлять сообщения по-одиночному, для этого замените на await message.send_to(476393332)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file async_vksher-2.2.2.tar.gz
.
File metadata
- Download URL: async_vksher-2.2.2.tar.gz
- Upload date:
- Size: 42.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a165e293901442d859b33e83bc9ff73448cc74e5dafc401b65adf1d9c14e2734 |
|
MD5 | 271a1eff148a05f75585ce3854a250d5 |
|
BLAKE2b-256 | a13f3d3eb794080cd65ae6bc644f4023f87ab33a4bcd294e5345f0cd1e5ea235 |
File details
Details for the file async_VKsher-2.2.2-py3-none-any.whl
.
File metadata
- Download URL: async_VKsher-2.2.2-py3-none-any.whl
- Upload date:
- Size: 29.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 669ad08785dcd19963135d733acf9622f7b715aa437945657fad01c0d7c8a36d |
|
MD5 | 1469acd88a0731e8b02433f5812a46e4 |
|
BLAKE2b-256 | 6dfd87e808416ba651a94fe3a9e54d66cafe2ac4d736e11c3a563cc3a8923e59 |