LINE, платформа обмена сообщениями, упаковала свой API обмена сообщениями во многие SDK, включая Python SDK. В его документации подразумевается, что API обмена сообщениями предназначен для использования разработчиками ботов, а SDK действительно предоставляет множество интерфейсов для функциональных возможностей API платформы LINE, которые полезны, но в некоторой степени полезны только для разработчиков ботов (по крайней мере, для меня). . Я начал возиться с ботом около трех лет назад. Исходя из своего опыта, я придумал множество подходов, которые помогут мне достичь лучших практик создания ботов. Большинство из них казались мне достаточно хорошими… пока я не столкнулся с множеством новых проблем, поскольку боты, которых я делал, со временем становились все сложнее.

Я разрабатывал ботов на микрофреймворке Flask. Однажды, когда я разрабатывал бота, я понял, что один конкретный декоратор фреймворка, которым я регулярно пользовался, а именно route, очень удобный декоратор, и я подумал, что он может мне помочь. route украшает функцию, чтобы иметь возможность обрабатывать запрос, поступающий с определенного URL-адреса с определенной конечной точкой (Flask использовал термин правило URL для пары конечной точки и функции). Декоратор, безусловно, очень удобен, поскольку позволяет связать любую функцию в любом модуле с правилом URL. Декоратор вдохновил меня сделать такой же механизм обработки запросов, которые приходят к боту. Итак, я начал делать декоратор, который превращает функции в обработчики определенных событий, которые происходят с ботом.

Прежде чем мы пойдем дальше, я должен сказать вам, что бот, который я разработал, не является ботом НЛП. Под этим я подразумеваю, что бот будет отвечать на некоторые предопределенные точные команды/запросы вместо того, чтобы отвечать на команду, подразумеваемую на естественном языке. Таким образом, декоратор лучше всего использовать на боте, не поддерживающем НЛП.

Идея состоит в том, чтобы создать декоратор, который принимает событие в качестве аргумента, а затем украшает им функцию, чтобы функция могла быть обработчиком этого события. Python SDK API обмена сообщениями LINE имеет аналогичный декоратор с именем add, который доступен как метод в классе WebhookHandler, но он полезен только для различения событий на уровне типа. Он не дает возможности различать события на более контекстуальном уровне. Есть (по крайней мере) два типа событий, которые необходимо различать на их контекстуальном уровне. Это MessageEvent и PostbackEvent. Например, WebhookHandler.add может обозначить функцию для обработки MessageEvent типом TextMessage, но не может заставить функцию обрабатывать только сообщения, содержащие определенный текст. Вы также не можете украсить более одной функции одним и тем же типом события. Если вы это сделаете, последняя функция, украшенная декоратором с определенным типом события, перезапишет предыдущую украшенную функцию в качестве обработчика). Вот пример использования декоратора WebhookHandler.add.

Упомянутое поведение декоратора приводит к двум проблемам. Во-первых, все те же события, которые привязаны к декоратору, будут направляться в одну и ту же ассоциированную функцию. Во-вторых, после того, как вы получили событие в связанной функции, вы должны запустить текст сообщения (event.message.text) через серию условных операторов, чтобы узнать, какой блок кода нужно запустить. Первая проблема вызывает настоящую боль, когда вы хотите разместить свои функции в некоторых контекстно-организованных модулях, потому что вы не можете определить функцию в разных модулях; все MessageEvent должны обрабатываться одной и той же функцией. Последнее повлияет на читаемость вашего кода из-за чрезмерного количества блоков условных операторов.

Вернемся к декоратору, который я разработал. Я понял, что декоратор должен уметь обрабатывать события на более контекстуальном уровне. Таким образом, я сделал так, чтобы декоратор принимал в качестве аргумента идентификатор контекста события. В моем случае мне нужно только MessageEvent с TextMessage в качестве типа сообщения и PostbackEvent для обработки на более контекстуальном уровне. Таким образом, идентификатор контекста для этих двух Event унаследованных объектов может быть строкой.

Из приведенного выше примера мы можем видеть, что теперь декоратор оформляет первую функцию как обработчик MessageEvent с TextMessage в качестве типа сообщения и /foo в качестве команды. Функция, которая имеет /bar в качестве своей команды, не перезапишет первую функцию. Различение событий, которые приходят со строковой командой, по ее команде, дает нам возможность упорядочить функцию по модулям, которые организованы по функциям контекста команды, вместо того, чтобы различать ее по типу события.

Так как же работает декоратор?

Я упоминал ранее, что меня вдохновил декоратор Flask.route. Чтобы знать, какую функцию (или функция просмотра, как Flask использует ее в качестве термина) для запуска для определенной конечной точки URL, приложение Flask хранит словарь правил URL (Flask.view_functions). Словарь имеет конечную точку URL в качестве ключа и соответствующий объект функции представления в качестве значения. Я намеревался реализовать такую ​​же идею в декораторе, который я разработал. Декоратор add в WebhookHandler также реализует ту же идею, сохраняя словарь, который использует имя функции обработки в качестве ключа и саму функцию обработки в качестве значения, но такая же реализация, как route, не позволяет add различать события в более контекстуальный уровень. Итак, я намеревался реализовать, более или менее, ту же идею, но с некоторыми изменениями, чтобы преодолеть проблему контекстуального уровня.

Как вы уже поняли из предыдущего примера, декоратор — это метод переменной с именем bot. Переменная является экземпляром класса Bot, который имеет атрибуты с именами tasks и rules. Я написал строку документации класса, которая в значительной степени иллюстрирует работу класса и декоратора.

Итак, как узнать, какую функцию запускать при наличии команды? Метод handle ищет в словаре tasks имя функции, соответствующее полученной команде или событию. После получения имени функции имя функции используется для поиска соответствующего объекта функции в словаре rules. Вот код для handle.

Вам может быть интересно, почему должно быть два словаря поиска: tasks и rules. Реализация двух словарей полезна для функции redirect в классе Bot. Меня также вдохновляет функция Flask redirect, которая может перенаправлять пользователей на любую допустимую конечную точку URL. Итак, я также сделал функцию перенаправления для класса Bot, но функция перенаправления, конечно, не будет принимать конечную точку URL в качестве аргумента. Когда я писал Bot.redirect, мне пришло в голову, что функция должна искать в словаре tasks, чтобы получить функцию для перенаправления. Вот где закралась проблема. Когда вы хотите перенаправить на другую функцию правила, ища функцию в задачах, вы не можете сформировать ключ, необходимый для поиска задач. словарь, потому что такие ключи могут быть сформированы только тогда, когда действительно произошло соответствующее событие. Таким образом, функция должна вместо этого принимать имя функции для перенаправления в качестве аргумента. Таким образом, метод Bot.redirect может искать в словаре rules вместо поиска в словаре rules.

Здесь у нас есть определенный класс с тремя функциями, которые полезны при работе с API обмена сообщениями LINE и Flask. Я планировал создать проект, чтобы добавить больше функций в класс Bot, чтобы сделать его еще лучше, поэтому, возможно, эти функции можно будет выпустить в виде реального расширения Flask. Я открыт для любых предложений и критики для этого будущего проекта!