사용자 정의 자동화 만들기

: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

field

field :name, component:는 자동화 UI에 사용자 정의 값을 추가할 수 있게 해줍니다.

유효한 컴포넌트 목록:

# 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: Category
               description: Optional, allows to limit trigger execution to this category

여기서 description은 선택 사항임을 유의하세요.


이 문서는 버전 관리됩니다 - 변경 사항을 github에서 제안하십시오.

9개의 좋아요

When I saw this was a new topic I got excited: I thought more details had been shared! :laughing:

As someone who does not program in Ruby, but very interested in workflow automation, I was hoping I might grok a little bit more by example…

:thinking:

Guess I’ll need to start at Developing Discourse Plugins - Part 1 - Create a basic plugin:sweat_smile:

8개의 좋아요

I essentially just chopped this section from the plugin topic so it didn’t appear as if you needed to know it to make use of the existing ones. :slight_smile:

I agree that it would be great if this was a little bit more of a step-by-step. I’ve sent out a flare for community assistance here to see if anyone has experience of such things: :crossed_fingers:

5개의 좋아요

A hello world example would be cool.
Where should the scripts be stored? Would like to experiment with it a bit.

4개의 좋아요

I think you can write custom scripts using Chat GPT along with this plugin.

Probably the best place to start looking is in the automation script that’s added to the Data Explorer plugin: discourse-data-explorer/plugin.rb at main · discourse/discourse-data-explorer · GitHub. It’s also worth looking at the Automation plugin’s existing scripts and triggers: https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation

Since there’s not much information on Meta about adding custom automations, here’s an example plugin.rb file that adds a script to update a user’s Activity Summary email preference. The script can be triggered by the Automation plugin’s ‘user_added_to_group’ or ‘user_removed_from_group’ triggers.

# frozen_string_literal: true

# name: automation-script-example
# about: An example of how to add a script to an automation
# 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

The full plugin code is here: GitHub - scossar/automation-script-example: An example of how to add a custom script to the Discourse Automation plugin. · GitHub.

:warning: please don’t use this code as it is on a production site. I hadn’t looked at the Automation code before this evening. If I get any feedback about potential issues with the code, I’ll update this post and the GitHub repo.

Edit: my concern was how to best deal with the case of multiple automation scripts being triggered by either the ‘user_added_to_group’ or ‘user_removed_from_group’ triggers. The initial version of the plugin was checking for:

fields.has_key?("email_digests")

but that felt kind of flaky. What if another script was added that also had an email_digests key?

The updated code passes the automation parameter to the code block and checks:

automation.script == "user_update_summary_email_options"

That should ensure that the script won’t be run for the wrong automation.

… thinking about it some more, it’s unlikely the script could get triggered by an automation it wasn’t configured for :slight_smile:

7개의 좋아요

I’d like to know this too - once you’ve made a repo like @simon’s, how does it get accessed by the plugin?

Do we have to fork the whole plugin and drop it in with the existing ones in https://github.com/discourse/discourse-automation/tree/main/lib/discourse_automation/scripts? Or is there a more elegant way?

1개의 좋아요

You need to install it like any other Discourse plugin: Install Plugins in Discourse. So you would install the Automation plugin and install your plugin that adds the custom scripts. The reason it works is because of the methods defined here: https://github.com/discourse/discourse-automation/blob/main/lib/plugin/instance.rb. In the example code I posted above, you’ll see that the custom script is being added with a call to add_automation_scriptable.

Note: don’t install the example automation from my github repo, just take it as an example of how to extend the Automation plugin. (I forgot I’d linked to it here and updated it so that it only works with my forked version of the Discourse Automation plugin. The code I linked to here is still valid though: Create custom Automations - #6 by simon. I’ll update the automation-script-example plugin ASAP so that it works without the changes I made to my forked version of the Automation plugin.)

My concern was unfounded. This condition isn’t necessary:

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

I’ll update the example soon.

4개의 좋아요

Am I correct in understanding that custom automations require a self-hosted installation (or otherwise direct backend access to the filesystem where Discourse is installed)?

2개의 좋아요

Yes, however we are very open to merging new automation scripts the community build , what are you thinking of building?

6개의 좋아요

Specifically, we’re looking for a way to replace some specific string in posts (we don’t care strongly about the exact syntax, but something like the plaintext @ref `Random.rand!` ) with a formatted link like Random.rand!. Looking up the exact URL is a complicated process with tens of thousands of possible targets that is completely infeasible with regexes (like Auto linkify words/watched words does) and much easier with a Turing-complete plugin-like environment… so I was curious if automations could do this.

So I was looking for a post-edit action, somewhat akin to what the @system user does when you quote the entire previous post (see here). It’d be an “Edit post” script that would trigger on “Post created” (or perhaps after-post-cooked)… but I suppose that the automations framework wouldn’t allow such a general “post-edit” action. I think it’d need to be a pretty specific link-to-Julia-docs custom automation, which certainly doesn’t make sense in a community build.

I could be barking up the wrong tree here with custom automations; I was just exploring what’s possible. Of course, as a forum for programming language enthusiasts, intrepid users are already thinking about programming bots that could use the discourse API to do this.

1개의 좋아요

I am not sure automation is what you are after here cause something that feels critical here is the “end user” experience. With automation this would only be replaced after the fact.

Thinking through this type of problem, I would probably recommend going with either a custom plugin or a theme component.

A theme component could work like so:

  1. User types: ^Rand
  2. An HTTP call is made to a backend service you host that lists all the options with URLs
  3. User selects the one they want and hit enter
  4. Markdown is swapped to [Random.rand!](https://docs.julialang.org/en/v1/stdlib/Random/#Random.rand!)

A plugin that amend the markdown pipeline could work similar to onebox and just autolink as you type leaving the original syntax. eg: ^Random.rand

I hear you on linkify not being ideal, discovery is hard, plus you may have to host a website so you normalize it to lookup.docs.julialang.org?q=Random.rand!

Certainly a very interesting problem. I think the UX of a theme component can be reasonable here.

2개의 좋아요

That is very awesome, thank you for the thoughts and pointers here! I’ll take this to a separate topic if and when I have more questions (or answers :slight_smile: ).

2개의 좋아요

I think a custom plugin that fires on post change is what you want to create. I suspect for your use case, which has a simple trigger, it would be easier to write a plugin rather than full with the automation plugin.

1개의 좋아요

@McUles and @nathank have you found the information you were looking for?

To make the custom automation works, I have had to modify the following files:

Created the custom automation script

Updated: server.en.yml

added custom automation name; title; and description on the scriptables section of the yml file.

Updated: client.en.yml

added custom automation name on scriptables; add the ‘field’ keyword; inside field keyword add ‘field_name’ followed by ‘label’ and ‘description’

Updated: scripts.rb

add the custom automation name in the list of scripts. Sample: FILE_NAME = “file_name”

Updated: plugin.rb

inside ‘after_initialize do’, add the path to the custom automation script. Sample: ‘lib/discourse_automation/scripts/file_name’

I don’t fully understand what you have put there - are these modifications to the Automations plugin, or important components of a sister plugin which contains the custom automation?

It would be great to have this coalesced into the OP

2개의 좋아요

It’s that. I didn’t really know the answer to your question, so here’s how I found an answer and also the answer to “Is there an example, somewhere, that I could see?”

First, get this: GitHub - discourse/all-the-plugins · GitHub

Then you grep for something, like “add_automation_scriptable”, and then you can find out what’s using it.

 (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

So maybe look at discourse-assign or data-explorer

2개의 좋아요