Package: async-VKsher
asyncVK – асинхронный фреймворк для создания ботов ВК. Преимущества: удобство, скорость выигрываемая за счёт асинхронности.Бот создаётся за счёт шести структурных единиц:Bot – это самая главная структурная единица. Это собственно сам бот, который подаёт ивенты     обработчикам.Handler – эта структурная единица отвечает за обработку ивентов.Dispatcher – эта структурная единица отвечает за взаимодействие с ВК (ответы на сообщения, добавление комментариев). Она автоматически настраивается хандлерами.Keyboard – это второстепенная структурная единица. Она отвечает за создание кнопок в ВК.Condition (Condition, And, Or) – эта структурная единица отвечает за условия. С помощью неё можно строить сложные условия для хандлеров.Chain - эта структурная единица позволяет создавать цепочки команд.Как работать с библиотекой? Легко и интуитивно понятно! Для начала нужно импортировать саму библиотеку и создать бота:fromasyncVKimportHandler,Bot,run_pollingfromasyncVK.dispatcherimportDispatcherfromasyncVK.conditionimportCondition,And,OrimportasyncVK.keyboardaskeyboardTOKEN="access_token"GROUP_ID=182801600bot=Bot(TOKEN,GROUP_ID)Теперь мы можем запустить бота наLongPoll API:if__name__=="__main__":run_polling(bot)Сейчас бот запущен, но ни на что не реагирует. Чтобы это исправить нам нужно создать обработчик и добавить его в бота. Как это сделать? Вот так:@bot.handle@Handler.on.message_new(Condition(command="привет!"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):awaitdispatcher.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!". Как сделать чтобы она ответила не в тот же чат, а в ЛС? Легко! Заменитьawaitdispatcher.send_message("Hi!")наawaitdispatcher.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 – кнопки ВК.
	
dispatcher.send_message – ответить в том же чате. Список аргументов идентичен с answer.
dispatcher.send_comment – ответить в комментариях. Список аргументов идентичен с answer, но аргумент keyboard отсутствует.
dispatcher.mark_as_read – пометить сообщение как "прочитанное". Никаких аргументов не принимает.
dispatcher.set_typing_status – установить статус на набор текста / запись голосового сообщения. Принимает один аргумент: 
    typing_status. Его значение по умолчанию "typing" (набор текста). Можно изменить на "audiomessage" – запись голосового сообщения.Как создать кнопки? Тоже несложно! Вот пример:@bot.handle@Handler.on.message_new(Condition(contains_command="прив"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):buttons=keyboard.get_keyboard([[("yes","positive"),("no","negative")],[("hm...","default"),("by default","primary"),("never","negative")]])awaitdispatcher.send_message("Содержит сообщение прив",keyboard=buttons)Результат создания кнопокПо умолчанию кнопки создаются обычными и одноразовыми. Чтобы сделать их многоразовыми пропишитеone_time=False, а если хотите сделать их инлайновыми, то пропишитеinline=True. К примеру:buttons=keyboard.get_keyboard([[("yes","positive"),("no","negative")],[("hm...","default"),("by default","primary"),("never","negative")]],inline=True)Тогда эти кнопки будут в сообщении (инлайновыми).Можно строить любые запросы, даже если этого не предполагает отсутствие метода в диспетчере:@bot.handle@Handler.on.message_new(Condition(contains_command="прив"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):result=awaitbot.execute("messages.send",peer_id=dispatcher.peer_id,message="okay",random_id=0)print(result)В этом примере мы на новое сообщение, содержащее "прив" отвечаем "okay" нашим построенным запросом.peer_idже берём из диспетчера. Какие параметры можно высунуть из диспетчера? Можно высунуть:token,user_id,peer_id,post_id(если событие это новая запись на стене, новый комментарий на стене или в обсуждении),owner_id(если событие было внутри группы, тоowner_idэто id группы),event(объект, в котором содержится вся информация о событии) иtext(если к примеру событие это новое сообщение, тоtextэто текст сообщения, если это к примеру новый комментарий, тоtextэто текст комментария и т.д.)Если вы хотите выполнить сразу несколько запросов асинхронно, то можно просто воспользовать библиотекойasyncio. К примеру:@bot.handle@Handler.on.message_new(Condition(contains_command="прив"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):tasks=[asyncio.create_task(dispatcher.mark_as_read()),asyncio.create_task(dispatcher.set_typing_status()),asyncio.create_task(asyncio.sleep(9))]awaitasyncio.gather(*tasks)awaitdispatcher.send_message("okay")@bot.handle@Handler.on.message_new(Condition(contains_command="а"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):awaitdispatcher.send_message("Б!")Хандлер, обрабатывающий сообщение, где есть строка "прив" сперва пометит сообщение как прочитанное, потом установит статус "печатает…" и через 9 секунд отправит сообщение "okay" и всё это асинхронно.P.S. хандлеры друг друга не блокируют, так что во время работы первого хандлера вы можете написать "а" и бот ответит "Б!", несмотря на работу первого хандлера.По-мимо этого можно делать хандлеры не для условий, а для всего события целиком. Например:@bot.handle@Handler.on("message_new")asyncdefhandler(dispatcher:Dispatcher):ifdispatcher.text.lower()=="abs":awaitdispatcher.send_message("peer")elifdispatcher.text.lower()=="help me":awaitdispatcher.send_message("no")Этот хандлер будет обрабатывать все события типаmessage_new. В данном случае он на "abs" будет отвечать "peer", а на "help me" будет отвечать "no". И также регистр сообщения не важен, ибо мы применили методlower.Так можно делать обработчики для любых событий. К примеру обработчик для новых комментариев:@bot.handle@Handler.on("wall_reply_new")asyncdefhandler(dispatcher:Dispatcher):ifdispatcher.text.lower()=="nice":awaitdispatcher.send_comment("ok")elifdispatcher.text.lower()=="not bad":awaitdispatcher.send_comment("no, very bad!")Какой обработчик использовать? Для условий или для всего события целиком? Если вам нужно сделать обработчик для простых команд (ответить на то этим и что-то в этом роде), то лучше все эти команды прописать в обработчике события, в данном случае это будет@Handler.on("message_new")ведь нам нужно отвечать на сообщения. А если же команды сложные, а не простые ответы с какими-то дополнительными действиями, то лучше их прописать в обработчике условия. К примеру нам нужно, чтобы при сообщении "статистика" бот получил статистику откуда-то, рассортировал и отфильтровал её и потом отправил. Такое лучше прописывать в обработчике условия, в данном случае@Handler.on.message_new(Condition(command="статистика"))Но нужно смотреть на код в целом, ибо иногда может пригодится сделать исключение и написать сложную команду в обработчик события, а простую в обработчик условия. То есть выбор должен зависеть от ситуации и структуры вашего кода.В хандлерах и в самом боте не предусмотрена синхронизация. Поэтому если вы будете пользоваться асинхронной реализацией, к примеру какой-то базы-данных, будет состояние гонки. А пользоваться синхронными реализациями базы-данных плохая идея, это снизит скорость бота. Такая структура позволяет боту быть очень быстрым. Но в фреймворке есть реализация асинхронной базы-данных с синхронизацией, которой если вы будете правильно пользоваться, то состояния гонки не будет и бот будет оставаться таким же быстрым. Пример бота с этой реализацией бд:fromasyncVK.asyncDBimportSQLitedb=SQLite("data.db")bot=Bot(TOKEN,GROUP_ID)asyncdefcreate_db():asyncwithdb:awaitdb.execute("""CREATE TABLE IF NOT EXISTS profile (user_id INTEGER,money INTEGER)""")@bot.handle@Handler.on("message_new")asyncdefhandler(dispatcher:Dispatcher):ifdispatcher.text.lower()=="create db"anddispatcher.user_id==OWNER_ID:awaitcreate_db()awaitdispatcher.send_message("db was created!")elifdispatcher.text.lower()=="register":asyncwithdb:awaitdb.execute("""INSERT INTO profileVALUES (?, 0)""",(dispatcher.user_id,))awaitdispatcher.send_message("you are was registered!")@bot.handle@Handler.on.message_new(Condition(command="click"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):asyncwithdb:awaitdb.execute("""UPDATE profileSET money=money+1WHERE user_id=(?)""",(dispatcher.user_id,))state=awaitdb.execute("""SELECT moneyFROM profileWHERE user_id=(?)""",(dispatcher.user_id,))money=state[0][0]awaitdispatcher.send_message(f"Money:{money}")OWNER_IDэто константа, которая должна ваш ID в ВК, это условие запрещает создавать база-данных кому-либо кроме вас командой.
Что делаетasync with db?.async with dbждёт пока база-данных откроется для запросов, потом закрывает базу-данных для запросов и как все ваши запросы прошли к базе, она опять открывает базу-данных для запросов.
Методdb.executeотправляет ваш запрос к базе-данных.Также можно использовать глобальные переменные с синхронизацией. Вот пример:fromasyncVK.asyncDBimportVariabletotal_money=Variable(0)@bot.handle@Handler.on.message_new(Condition(command="/click"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):asyncwithtotal_money:total_money.object+=1awaitdispatcher.send_message("Все деньги мира: "+str(total_money.object))В фреймворке также присутствует встроенный функционал создания цепочек. Что такое цепочки? Это когда команда состоит из нескольких частей. То есть, к примеру, регистрация. Вы пишите/регистрацияи бот далее просит вас ввести имя. И вы вводите своё имя и регистрация пройдена в 2 сообщения, то есть 2 части.fromasyncVK.chainimportChainТаким образом мы импортируем класс цепочек. Как создать цепочку? Всё просто. Вместоbot.handleиспользуемchain.add_handler, а потомbot.add_chain(chain).К примеру:chain=Chain()@chain.add_handle@Handler.on.message_new(Condition(contains_command="прив"),is_lower=True)asyncdefhandler_1(dispatcher:Dispatcher):awaitdispatcher.send_message("Напиши что-то")@chain.add_handle@Handler.on.message_new(Condition(contains_command="что-то"),is_lower=True)asyncdefhandler_2(dispatcher:Dispatcher):awaitdispatcher.send_message("Пон")bot.add_chain(chain)Или:chain=Chain()@Handler.on.message_new(Condition(contains_command="прив"),is_lower=True)asyncdefhandler_1(dispatcher:Dispatcher):awaitdispatcher.send_message("Напиши что-то")@Handler.on.message_new(Condition(contains_command="что-то"),is_lower=True)asyncdefhandler_2(dispatcher:Dispatcher):awaitdispatcher.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)asyncdefhandler_1(dispatcher:Dispatcher):awaitdispatcher.send_message("Напиши что-то")return12@Handler.on.message_new(Condition(contains_command="что-то"),is_lower=True)asyncdefhandler_2(dispatcher:Dispatcher):awaitdispatcher.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не сбрасывается.К примеруfromasyncVK.chainimportChain,Resetchain=Chain()@Handler.on.message_new(Condition(contains_command="прив"),is_lower=True)asyncdefhandler_1(dispatcher:Dispatcher):awaitdispatcher.send_message("Напиши что-то")@Handler.on.message_new(is_lower=True)asyncdefhandler_2(dispatcher:Dispatcher):ifdispatcher.text!="что-то":awaitdispatcher.send_message("Я жду от тебя что-то")returnReset()awaitdispatcher.send_message("Пон")if__name__=="__main__":chain=Chain()chain.add_handler(handler_1)chain.add_handler(handler_2)bot.add_chain(chain)run_polling(bot)Во втором хандлере бот будет ждать от тебя слова что-то, и пока ты его не напишешь - он будет срабатывать снова и снова в этой цепочке.Весь код целиком для старта:fromasyncVKimportHandler,Bot,run_pollingfromasyncVK.dispatcherimportDispatcherfromasyncVK.conditionimportCondition,And,OrimportasyncVK.keyboardaskeyboardTOKEN="access_token"GROUP_ID=182801600bot=Bot(TOKEN,GROUP_ID)@bot.handle@Handler.on.message_new(Condition(contains_command="прив"),is_lower=True)asyncdefhandler(dispatcher:Dispatcher):buttons=keyboard.get_keyboard([[("yes","positive"),("no","negative")],[("hm...","default"),("by default","primary"),("never","negative")]],inline=True)awaitdispatcher.send_message("Содержит сообщение прив",keyboard=buttons)result=awaitbot.execute("messages.send",peer_id=dispatcher.peer_id,message="okay",random_id=0)print(result)if__name__=="__main__":run_polling(bot)