Создавайте собственные автоматизации

:information_source: Это черновик, и он может потребовать дополнительной доработки.

Словарь

  • trigger (триггер): представляет имя триггера, например: user_added_to_group
  • triggerable (обработчик триггера): представляет логику кода, связанную с триггером, например: triggers/user_added_to_group_.rb
  • script (скрипт): представляет имя скрипта, например: send_pms
  • scriptable (обработчик скрипта): представляет логику кода, связанную со скриптом, например: scripts/send_pms.rb

API плагинов

add_automation_scriptable(name, &block)
add_automation_triggerable(name, &block)

API обработчиков скриптов (Scriptable)

field (поле)

field :name, component: позволяет добавить настраиваемое значение в интерфейс автоматизации.

Список допустимых компонентов:

# foo должен быть уникальным и представлять имя вашего поля.

field :foo, component: :text # создает текстовое поле ввода
field :foo, component: :list # создает поле ввода с множественным выбором, где пользователи могут вводить значения
field :foo, component: :choices, extra: { content: [ {id: 1, name: 'your.own.i18n.key.path' } ] } # создает выпадающий список с пользовательским содержимым
field :foo, component: :boolean # создает флажок
field :foo, component: :category # создает выбор категории
field :foo, component: :group # создает выбор группы
field :foo, component: :date_time # создает выбор даты и времени
field :foo, component: :tags # создает выбор тегов
field :foo, component: :user  # создает выбор пользователя
field :foo, component: :pms  # позволяет создать одну или несколько шаблонов личных сообщений
field :foo, component: :categories  # позволяет выбрать ноль или более категорий
field :foo, component: :key-value  # позволяет создавать пары ключ-значение
field :foo, component: :message  # позволяет составить личное сообщение с заменяемыми переменными
field :foo, component: :trustlevel  # позволяет выбрать один или несколько уровней доверия
triggerables и triggerable!
# Позволяет определить список допустимых обработчиков триггеров для скрипта
triggerables %i[recurring]

# Позволяет принудительно назначить обработчик триггера для вашего скрипта, а также принудительно задать состояние полей
field :recurring, component: :boolean
triggerable! :recurring, state: { foo: false }
placeholders (заполнители)
# Позволяет пометить ключ как заменяемый в текстах с использованием синтаксиса заполнителей `%%sender%%`
placeholder :sender

Обратите внимание, что скрипт несет ответственность за предоставление значений для заполнителей и применение замены с помощью input = utils.apply_placeholders(input, { sender: 'bob' }).

script (скрипт)

Это сердце автоматизации, где происходит вся логика.

# context передается при срабатывании автоматизации и может сильно различаться в зависимости от триггера
script do |context, fields, automation|
end

Локализация

Каждое используемое вами поле зависит от ключей i18n и будет находиться в пространстве имен, соответствующем его триггеру/скрипту.

Например, обработчик скрипта со следующим содержимым:

field :post_created_edited, component: :category

потребует наличия следующих ключей в файле client.en.yml:

en:
  js:
    discourse_automation:
      scriptables:
        post_created_edited:
          fields:
            restricted_category:
              label: Категория
               description: Опционально, позволяет ограничить выполнение триггера этой категорией

Обратите внимание, что поле description здесь опционально.


Этот документ контролируется версионированием — предлагайте изменения на GitHub.

9 лайков

Когда я увидел, что это новая тема, я обрадовался: подумал, что были опубликованы дополнительные детали! :laughing:

Как человек, который не программирует на Ruby, но очень заинтересован в автоматизации рабочих процессов, я надеялся, что смогу понять чуть больше на примерах…

:thinking:

Похоже, мне нужно начать с Developing Discourse Plugins - Part 1 - Create a basic plugin:sweat_smile:

8 лайков

По сути, я просто вырезал этот раздел из темы плагина, чтобы не создавалось впечатление, будто для использования существующих решений вам нужно это знать. :slight_smile:

Я согласен, что было бы здорово, если бы это было немного более пошагово. Я отправил запрос о помощи сообществу, чтобы узнать, есть ли у кого-то опыт в таких вещах: :crossed_fingers:

5 лайков

Пример «Hello World» был бы крут.
Где должны храниться скрипты? Хотелось бы немного поэкспериментировать.

4 лайка

Я думаю, что вы можете писать пользовательские скрипты с помощью Chat GPT вместе с этим плагином.

Вероятно, лучшее место для начала поиска — это скрипт автоматизации, добавленный в плагин Data Explorer: discourse-data-explorer/plugin.rb at main · discourse/discourse-data-explorer · GitHub. Также стоит посмотреть на существующие скрипты и триггеры плагина Automation: https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation.

Поскольку в разделе Meta информации о добавлении пользовательских автоматизаций мало, вот пример файла plugin.rb, который добавляет скрипт для обновления предпочтений пользователя в отношении сводного email-рассылки активности. Этот скрипт может быть запущен триггерами плагина Automation «user_added_to_group» или «user_removed_from_group».

# frozen_string_literal: true

# name: automation-script-example
# about: Пример того, как добавить скрипт в автоматизацию
# version: 0.0.1
# authors: scossar

enabled_site_setting :automation_script_example_enabled

after_initialize do
  reloadable_patch do
    if defined?(DiscourseAutomation)
      DiscourseAutomation::Scriptable::USER_UPDATE_SUMMARY_EMAIL_OPTIONS =
        "user_update_summary_email_options"
      add_automation_scriptable(
        DiscourseAutomation::Scriptable::USER_UPDATE_SUMMARY_EMAIL_OPTIONS
      ) do

        field :email_digests, component: :boolean

        version 1
        triggerables [:user_added_to_group, :user_removed_from_group]

        script do |context, fields, automation|
          if automation.script == "user_update_summary_email_options" && (context["kind"] == "user_added_to_group" || context["kind"] == "user_removed_from_group")
            user_id = context["user"].id
            digest_option = fields.dig("email_digests", "value")
            user_option = UserOption.find_by(user_id: user_id)

            if (user_option)
              user_option.update(email_digests: digest_option)
            end
          end
        end
      end
    end
  end
end

Полный код плагина доступен здесь: GitHub - scossar/automation-script-example: An example of how to add a custom script to the Discourse Automation plugin. · GitHub.

:warning: Пожалуйста, не используйте этот код в таком виде на продакшн-сервере. Я не изучал код Automation до сегодняшнего вечера. Если я получу обратную связь о возможных проблемах с кодом, я обновлю этот пост и репозиторий на GitHub.

Редактирование: меня беспокоило, как лучше всего обрабатывать случай, когда несколько скриптов автоматизации запускаются триггерами «user_added_to_group» или «user_removed_from_group». Первоначальная версия плагина проверяла:

fields.has_key?("email_digests")

но это казалось ненадёжным. Что, если будет добавлен другой скрипт, у которого тоже есть ключ email_digests?

Обновлённый код передаёт параметр automation в блок кода и проверяет:

automation.script == "user_update_summary_email_options"

Это должно гарантировать, что скрипт не будет выполнен для неверной автоматизации.

… Подумав ещё немного, маловероятно, что скрипт может быть запущен автоматизацией, для которой он не был настроен :slight_smile:

7 лайков

Я тоже хотел бы это узнать — после того как вы создали репозиторий, подобный репозиторию @simon, как плагин получает к нему доступ?

Нам нужно форкнуть весь плагин и поместить его вместе с существующими в https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation/scripts? Или есть более изящный способ?

1 лайк

Вам нужно установить его как любой другой плагин Discourse: Установка плагинов в Discourse. Таким образом, вы установите плагин Automation и установите свой плагин, который добавляет пользовательские скрипты. Причина, по которой это работает, заключается в методах, определенных здесь: https://github.com/discourse/discourse-automation/blob/main/lib/plugin/instance.rb. В примере кода, который я опубликовал выше, вы увидите, что пользовательский скрипт добавляется с помощью вызова add_automation_scriptable.

Примечание: не устанавливайте пример автоматизации из моего репозитория на GitHub, используйте его только как пример того, как расширить плагин Automation. (Я забыл, что ссылался на него здесь, и обновил его так, чтобы он работал только с моей форкованной версией плагина Discourse Automation. Однако код, на который я ссылался здесь, по-прежнему актуален: Create custom Automations - #6 by simon. Я как можно скорее обновлю плагин automation-script-example, чтобы он работал без изменений, которые я внес в свою форкованную версию плагина Automation.)

Мои опасения были напрасны. Это условие не требуется:

if automation.script == "user_update_summary_email_options" && (context["kind"] == "user_added_to_group" || context["kind"] == "user_removed_from_group")

Я скоро обновлю пример.

4 лайка

Правильно ли я понимаю, что для создания пользовательских автоматизаций требуется самохостинг (или, в ином случае, прямой доступ к бэкенду к файловой системе, где установлен Discourse)?

2 лайка

Да, однако мы очень открыты к объединению новых скриптов автоматизации, созданных сообществом. Что вы планируете создавать?

6 лайков

Конкретно, мы ищем способ заменять определённые строки в постах (нас не слишком заботит точный синтаксис, например, простой текст @ref `Random.rand!` ) на форматированную ссылку вроде Random.rand!. Поиск точного URL — сложный процесс с десятками тысяч возможных целей, который полностью нереализуем с помощью регулярных выражений (как это делает автоматическая линковка слов/отслеживаемых слов), но гораздо проще в среде, похожей на плагины с поддержкой полных вычислений (Turing-complete)… поэтому меня заинтересовало, могут ли автоматизации справиться с этим.

Так что я искал действие по редактированию поста, нечто подобное тому, что делает пользователь @system, когда вы цитируете весь предыдущий пост (см. здесь). Это был бы скрипт «Редактировать пост», срабатывающий при «Создании поста» (или, возможно, после обработки поста)… но, полагаю, фреймворк автоматизаций не позволит такое общее действие «редактирование поста». Думаю, для этого потребуется довольно специфичная автоматизация для создания ссылок на документацию Julia, что, безусловно, не имеет смысла в сборке для сообщества.

Возможно, я иду не туда с кастомными автоматизациями; я просто исследовал, что возможно. Конечно, будучи форумом для энтузиастов языков программирования, смелые пользователи уже думают о написании ботов, которые могли бы использовать API Discourse для этого.

1 лайк

Не уверен, что автоматизация — это то, что вам нужно, поскольку здесь критически важен опыт «конечного пользователя». При автоматизации это будет заменено лишь постфактум.

Продумывая решение такой задачи, я бы порекомендовал использовать либо кастомный плагин, либо компонент темы.

Компонент темы мог бы работать следующим образом:

  1. Пользователь вводит: ^Rand
  2. Отправляется HTTP-запрос к вашему бэкенд-сервису, который возвращает список всех вариантов с URL
  3. Пользователь выбирает нужный вариант и нажимает Enter
  4. Markdown-разметка заменяется на [Random.rand!](https://docs.julialang.org/en/v1/stdlib/Random/#Random.rand!)

Плагин, изменяющий конвейер обработки Markdown, мог бы работать аналогично onebox: автоматически создавать ссылки по мере ввода, сохраняя исходный синтаксис. Например: ^Random.rand.

Я понимаю ваши опасения насчёт linkify: он не идеален, обнаружение функционала затруднено, плюс, возможно, потребуется разместить отдельный веб-сайт, чтобы нормализовать запросы до вида lookup.docs.julialang.org?q=Random.rand!.

Очень интересная задача. Думаю, что UX компонента темы здесь может быть вполне приемлемым.

2 лайка

Это очень здорово, спасибо за мысли и подсказки здесь! Я перенесу это в отдельную тему, если у меня появятся дополнительные вопросы (или ответы :).

2 лайка

Думаю, вам стоит создать собственный плагин, который срабатывает при изменении поста. Судя по вашему сценарию использования, где триггер простой, написать плагин будет проще, чем разбираться с плагином автоматизации.

1 лайк

@McUles и @nathank, нашли ли вы нужную информацию?

Чтобы пользовательская автоматизация работала, мне пришлось внести изменения в следующие файлы:

Создан скрипт пользовательской автоматизации

Обновлено: server.en.yml

В секции scriptables файла yml добавлены имя, заголовок и описание пользовательской автоматизации.

Обновлено: client.en.yml

В секции scriptables добавлено имя пользовательской автоматизации; добавлено ключевое слово «field»; внутри ключевого слова «field» добавлены «field_name», за которыми следуют «label» и «description».

Обновлено: scripts.rb

Добавлено имя пользовательской автоматизации в список скриптов. Пример: FILE_NAME = “file_name”.

Обновлено: plugin.rb

Внутри блока «after_initialize do» добавлен путь к скрипту пользовательской автоматизации. Пример: «lib/discourse_automation/scripts/file_name».

Я не до конца понимаю, что вы там указали — это модификации для плагина Automations или важные компоненты сестринского плагина, содержащего пользовательскую автоматизацию?

Будет здорово, если это будет объединено в первый пост.

2 лайка

Именно это. Я не знал точного ответа на ваш вопрос, поэтому вот как я нашёл ответ, а также ответ на вопрос: «Есть ли где-нибудь пример, который я мог бы посмотреть?».

Сначала получите это: GitHub - discourse/all-the-plugins · GitHub

Затем выполните grep по чему-то вроде «add_automation_scriptable», и вы сможете узнать, что его использует.

 (main) pfaffman@noreno:~/src/discourse-repos/all-the-plugins/official$ grep -r add_automation_scriptable
discourse-assign/plugin.rb:    add_automation_scriptable("random_assign") do
discourse-chat-integration/plugin.rb:    add_automation_scriptable("send_slack_message") do
discourse-chat-integration/plugin.rb:    add_automation_scriptable("send_chat_integration_message") do
discourse-data-explorer/plugin.rb:      add_automation_scriptable("recurring_data_explorer_result_pm") do
discourse-data-explorer/plugin.rb:      add_automation_scriptable("recurring_data_explorer_result_topic") do

Так что, возможно, стоит посмотреть на discourse-assign или data-explorer.

2 лайка